b_b 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,81 @@
1
+ module BB
2
+ class Builder
3
+ attr_reader :from, :group, :group_each, :limit, :omit_record_if,
4
+ :order, :select, :having, :where, :joins
5
+
6
+ attr_accessor :options
7
+
8
+ def initialize
9
+ @joins = []
10
+ @options = {}
11
+ end
12
+
13
+ def build
14
+ structure.flatten.compact.map(&:build).join(" ")
15
+ end
16
+
17
+ def assign(name)
18
+ get_factory_ivar(name).tap do |factory|
19
+ append_options!(factory)
20
+ end
21
+ end
22
+
23
+ def append_joins(name)
24
+ register_factory(name).tap do |factory|
25
+ append_options!(factory)
26
+ joins << factory
27
+ end
28
+ end
29
+
30
+ def add_option_to_just_before_join(rel)
31
+ return if joins.empty?
32
+
33
+ joins.last.options[:rel] = rel
34
+ end
35
+
36
+ def add_offset_to_limit(offset)
37
+ return if limit.nil?
38
+
39
+ limit.options[:offset] = offset
40
+ end
41
+
42
+ private
43
+
44
+ def structure
45
+ [select_clause, from, joins, omit_record_if, where, group_clause, having, order, limit]
46
+ end
47
+
48
+ def select_clause
49
+ select || unless from.nil?
50
+ get_factory_ivar(:select).tap do |factory|
51
+ factory.append_formatted_filters(%w(*))
52
+ end
53
+ end
54
+ end
55
+
56
+ def group_clause
57
+ group || group_each
58
+ end
59
+
60
+ def get_factory_ivar(name)
61
+ instance_variable_get("@#{name}") || set_factory_ivar(name)
62
+ end
63
+
64
+ def set_factory_ivar(name)
65
+ register_factory(name).tap do |factory|
66
+ instance_variable_set("@#{name}", factory)
67
+ end
68
+ end
69
+
70
+ def append_options!(factory)
71
+ return if options.empty?
72
+
73
+ factory.options.merge!(options)
74
+ options.clear
75
+ end
76
+
77
+ def register_factory(name)
78
+ Factory.build(name)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ module BB
2
+ class Component
3
+ TEMPLATE = {
4
+ cross_join: "CROSS JOIN %s AS %s",
5
+ from: "FROM %s",
6
+ from_union_all_with_alias: "FROM (SELECT * FROM %s) AS %s",
7
+ from_with_alias: "FROM %s AS %s",
8
+ full_outer_join_each: "FULL OUTER JOIN EACH %s AS %s ON %s",
9
+ group: "GROUP BY %s",
10
+ group_each: "GROUP EACH BY %s",
11
+ having: "HAVING %s",
12
+ inner_join: "INNER JOIN %s AS %s ON %s",
13
+ inner_join_each: "INNER JOIN EACH %s AS %s ON %s",
14
+ join: "JOIN %s AS %s ON %s",
15
+ join_each: "JOIN EACH %s AS %s ON %s",
16
+ left_join: "LEFT JOIN %s AS %s ON %s",
17
+ left_join_each: "LEFT JOIN EACH %s AS %s ON %s",
18
+ left_outer_join: "LEFT OUTER JOIN %s AS %s ON %s",
19
+ left_outer_join_each: "LEFT OUTER JOIN EACH %s AS %s ON %s",
20
+ limit: "LIMIT %d",
21
+ limit_with_offset: "LIMIT %d OFFSET %d",
22
+ omit_record_if: "OMIT RECORD IF %s",
23
+ order: "ORDER BY %s",
24
+ right_join: "RIGHT JOIN EACH %s AS %s ON %s",
25
+ right_join_each: "RIGHT JOIN EACH %s AS %s ON %s",
26
+ right_outer_join: "RIGHT OUTER JOIN EACH %s AS %s ON %s",
27
+ right_outer_join_each: "RIGHT OUTER JOIN EACH %s AS %s ON %s",
28
+ select: "SELECT %s",
29
+ where: "WHERE %s"
30
+ }.freeze
31
+
32
+ attr_accessor :filters, :options, :type
33
+
34
+ def initialize
35
+ @filters = []
36
+ @options = {}
37
+ end
38
+
39
+ def build
40
+ type && send("to_#{type}")
41
+ end
42
+
43
+ private
44
+
45
+ (API[:joins] - [:to_cross_join]).each do |name|
46
+ define_method("to_#{name}") do
47
+ format(TEMPLATE[name.to_sym], filters[0], options[:as], options[:rel])
48
+ end
49
+ end
50
+
51
+ %i(cross_join from_with_alias).each do |name|
52
+ define_method("to_#{name}") do
53
+ format(TEMPLATE[name.to_sym], filters[0], options[:as])
54
+ end
55
+ end
56
+
57
+ %i(from group group_each order select).each do |name|
58
+ define_method("to_#{name}") do
59
+ format(TEMPLATE[name.to_sym], filters.join(", "))
60
+ end
61
+ end
62
+
63
+ %i(having omit_record_if where).each do |name|
64
+ define_method("to_#{name}") do
65
+ format(TEMPLATE[name.to_sym], filters.join(" "))
66
+ end
67
+ end
68
+
69
+ def to_from_union_all_with_alias
70
+ format(TEMPLATE[:from_union_all_with_alias], filters.join(", "), options[:as])
71
+ end
72
+
73
+ def to_limit
74
+ format(TEMPLATE[:limit], filters.last)
75
+ end
76
+
77
+ def to_limit_with_offset
78
+ format(TEMPLATE[:limit_with_offset], filters.last, options[:offset])
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,87 @@
1
+ module BB
2
+ module Converter
3
+ class Formula
4
+ TEMPLATE = {
5
+ between: "%s BETWEEN %s",
6
+ contains: "%s CONTAINS %s",
7
+ equals: "%s = %s",
8
+ gt: "%s > %s",
9
+ gteq: "%s >= %s",
10
+ in: "%s IN %s",
11
+ is: "%s IS %s",
12
+ lt: "%s < %s",
13
+ lteq: "%s <= %s",
14
+ match: "REGEXP_MATCH(%s, %s)",
15
+ not_between: "NOT %s BETWEEN %s",
16
+ not_contains: "NOT %s CONTAINS %s",
17
+ not_equals: "%s <> %s",
18
+ not_gt: "NOT %s > %s",
19
+ not_gteq: "NOT %s >= %s",
20
+ not_in: "NOT %s IN %s",
21
+ not_is: "%s IS NOT %s",
22
+ not_lt: "NOT %s < %s",
23
+ not_lteq: "NOT %s <= %s",
24
+ not_match: "NOT REGEXP_MATCH(%s, %s)"
25
+ }.freeze
26
+
27
+ OPERATORS_DICTIONARY = {
28
+ cont: :contains,
29
+ contains: :contains,
30
+ eq: :equals,
31
+ eql: :equals,
32
+ equals: :equals,
33
+ gt: :gt,
34
+ gteq: :gteq,
35
+ like: :contains,
36
+ lt: :lt,
37
+ lteq: :lteq,
38
+ not_cont: :not_contains,
39
+ not_contains: :not_contains,
40
+ not_eq: :not_equals,
41
+ not_eql: :not_equals,
42
+ not_equals: :not_equals,
43
+ not_gt: :not_gt,
44
+ not_gteq: :not_gteq,
45
+ not_like: :not_contains,
46
+ not_lt: :not_lt,
47
+ not_lteq: :not_lteq
48
+ }.freeze
49
+
50
+ SEARCHABLE_COLUMN_FORMAT = /(?:(\w+)_(not_\w+)|(\w+)_(\w+))/
51
+
52
+ attr_reader :column, :operator, :value, :options
53
+
54
+ def initialize(column, value, options = {})
55
+ @column = format_column(column)
56
+ @value = format_value(value)
57
+ @options = options
58
+ end
59
+
60
+ def convert
61
+ evaluated_type = Evaluator::Formula.eval_type(value, options.merge(operator: operator))
62
+ evaluated_type && format(TEMPLATE[evaluated_type], column, value.convert)
63
+ end
64
+
65
+ private
66
+
67
+ def format_column(column)
68
+ scanned_column = column.to_s.scan(SEARCHABLE_COLUMN_FORMAT).flatten.compact
69
+ !scanned_column.empty? && format_operator(scanned_column[1]) ? scanned_column[0] : column
70
+ end
71
+
72
+ def format_operator(operator)
73
+ @operator = OPERATORS_DICTIONARY[operator.to_sym]
74
+ end
75
+
76
+ def format_value(value)
77
+ value.is_a?(Value) ? value : Value.new(value)
78
+ end
79
+
80
+ class << self
81
+ def convert(column, value, options = {})
82
+ new(column, value, options).convert
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,30 @@
1
+ module BB
2
+ module Converter
3
+ class Order
4
+ TEMPLATE = "%s %s".freeze
5
+
6
+ attr_reader :column, :sort_key
7
+
8
+ def initialize(column, options = {})
9
+ @column = column
10
+ @sort_key = format_sort_key(options[:sort_key])
11
+ end
12
+
13
+ def convert
14
+ format(TEMPLATE, column, sort_key)
15
+ end
16
+
17
+ private
18
+
19
+ def format_sort_key(sort_key)
20
+ sort_key.to_s.casecmp("DESC").zero? ? :DESC : :ASC
21
+ end
22
+
23
+ class << self
24
+ def convert(column, options = {})
25
+ new(column, options).convert
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ module BB
2
+ module Converter
3
+ class Table
4
+ TEMPLATE = {
5
+ subquery: "(%s)",
6
+ table_date_range: "TABLE_DATE_RANGE(%s, %s, %s)"
7
+ }.freeze
8
+
9
+ attr_reader :value, :options
10
+
11
+ def initialize(value, options = {})
12
+ @value = value
13
+ @options = options
14
+ end
15
+
16
+ def convert
17
+ evaluated_type = Evaluator::Table.eval_type(value, options)
18
+ evaluated_type && send("to_#{evaluated_type}")
19
+ end
20
+
21
+ private
22
+
23
+ def to_subquery
24
+ format(TEMPLATE[:subquery], value.to_sql)
25
+ end
26
+
27
+ def to_table_date
28
+ options[:on].strftime("#{value}%Y%m%d")
29
+ end
30
+
31
+ def to_table_date_range
32
+ from = Value.convert(options[:from], type: :timestamp)
33
+ to = Value.convert(options[:to], type: :timestamp)
34
+
35
+ format(TEMPLATE[:table_date_range], value, from, to)
36
+ end
37
+
38
+ def to_plain
39
+ value.to_s
40
+ end
41
+
42
+ class << self
43
+ def convert(value, options = {})
44
+ new(value, options).convert
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,99 @@
1
+ module BB
2
+ module Converter
3
+ class Value
4
+ TEMPLATE = {
5
+ array: "(%s)",
6
+ date: "DATE('%Y-%m-%d')",
7
+ null: "NULL",
8
+ range: "%s AND %s",
9
+ regexp: "r'%s'",
10
+ string: "'%s'",
11
+ subquery: "(%s)",
12
+ time: "TIMESTAMP('%Y-%m-%d %H:%M:%S')",
13
+ timestamp: "TIMESTAMP('%Y-%m-%d')"
14
+ }.freeze
15
+
16
+ attr_reader :value, :options, :type
17
+
18
+ def initialize(value, options = {})
19
+ @value = value
20
+ @options = options
21
+ @type = format_type
22
+ end
23
+
24
+ def convert
25
+ send("to_#{type}")
26
+ end
27
+
28
+ private
29
+
30
+ def format_type
31
+ options[:type] || Evaluator::Value.eval_type(value, options)
32
+ end
33
+
34
+ def to_array
35
+ format(TEMPLATE[:array], value.map(&method(:convert_member_value)).join(", "))
36
+ end
37
+
38
+ def to_boolean
39
+ value.to_s
40
+ end
41
+
42
+ alias_method(:to_numeric, :to_boolean)
43
+
44
+ def to_date
45
+ value.strftime(TEMPLATE[:date])
46
+ end
47
+
48
+ def to_time
49
+ value.strftime(TEMPLATE[:time])
50
+ end
51
+
52
+ def to_timestamp
53
+ value.strftime(TEMPLATE[:timestamp])
54
+ end
55
+
56
+ def to_null
57
+ TEMPLATE[:null]
58
+ end
59
+
60
+ def to_range
61
+ range_values = extract_range_values(value).map(&method(:convert_member_value))
62
+ format(TEMPLATE[:range], range_values[0], range_values[1])
63
+ end
64
+
65
+ def to_regexp
66
+ format(TEMPLATE[:regexp], value.inspect[1..-2])
67
+ end
68
+
69
+ def to_string
70
+ format(TEMPLATE[:string], value)
71
+ end
72
+
73
+ def to_subquery
74
+ format(TEMPLATE[:subquery], value.to_sql)
75
+ end
76
+
77
+ def convert_member_value(value)
78
+ self.class.convert(value, eval_types: %i(boolean date null numeric string time))
79
+ end
80
+
81
+ def extract_range_values(value)
82
+ [].tap do |arr|
83
+ arr << value.begin
84
+ arr << if value.exclude_end?
85
+ (!value.end.is_a?(Time) ? value.last(1)[0] : value.last - 1)
86
+ else
87
+ value.end
88
+ end
89
+ end
90
+ end
91
+
92
+ class << self
93
+ def convert(value, options = {})
94
+ new(value, options).convert
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,8 @@
1
+ module BB
2
+ module Converter
3
+ autoload :Formula, "b_b/converter/formula"
4
+ autoload :Order, "b_b/converter/order"
5
+ autoload :Table, "b_b/converter/table"
6
+ autoload :Value, "b_b/converter/value"
7
+ end
8
+ end
@@ -0,0 +1,81 @@
1
+ module BB
2
+ module Evaluator
3
+ class Formula
4
+ EVALUATE_TYPES = %i(between contains equals gt gteq in is lt lteq match).freeze
5
+
6
+ attr_reader :value, :operator, :negation
7
+
8
+ def initialize(value, options = {})
9
+ @value = value
10
+ @operator = options[:operator]
11
+ @negation = options[:negation]
12
+ end
13
+
14
+ def eval_type
15
+ evaluated_type = EVALUATE_TYPES.detect { |type| send("#{type}?") }
16
+
17
+ if !evaluated_type.nil? && negative? && !double_negative?
18
+ :"not_#{evaluated_type}"
19
+ else
20
+ evaluated_type
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def negative?
27
+ negation || operator.to_s.start_with?("not")
28
+ end
29
+
30
+ def double_negative?
31
+ negation && operator.to_s.start_with?("not")
32
+ end
33
+
34
+ def between?
35
+ value.type == :range
36
+ end
37
+
38
+ def contains?
39
+ %i(contains not_contains).include?(operator)
40
+ end
41
+
42
+ def equals?
43
+ %i(date numeric string time).include?(value.type) && (operator.nil? || %i(equals not_equals).include?(operator))
44
+ end
45
+
46
+ def gt?
47
+ %i(gt not_gt).include?(operator)
48
+ end
49
+
50
+ def gteq?
51
+ %i(gteq not_gteq).include?(operator)
52
+ end
53
+
54
+ def in?
55
+ %i(array subquery).include?(value.type)
56
+ end
57
+
58
+ def is?
59
+ %i(boolean null).include?(value.type)
60
+ end
61
+
62
+ def lt?
63
+ %i(lt not_lt).include?(operator)
64
+ end
65
+
66
+ def lteq?
67
+ %i(lteq not_lteq).include?(operator)
68
+ end
69
+
70
+ def match?
71
+ value.type == :regexp
72
+ end
73
+
74
+ class << self
75
+ def eval_type(value, options = {})
76
+ new(value, options).eval_type
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,42 @@
1
+ module BB
2
+ module Evaluator
3
+ class Table
4
+ EVALUATE_TYPES = %i(subquery table_date table_date_range plain).freeze
5
+
6
+ attr_reader :value, :options
7
+
8
+ def initialize(value, options = {})
9
+ @value = value
10
+ @options = options
11
+ end
12
+
13
+ def eval_type
14
+ EVALUATE_TYPES.detect { |type| send("#{type}?") }
15
+ end
16
+
17
+ private
18
+
19
+ def subquery?
20
+ value.respond_to?(:to_sql)
21
+ end
22
+
23
+ def table_date?
24
+ !value.to_s.empty? && options[:on].respond_to?(:strftime)
25
+ end
26
+
27
+ def table_date_range?
28
+ !value.to_s.empty? && options[:from].respond_to?(:strftime) && options[:to].respond_to?(:strftime)
29
+ end
30
+
31
+ def plain?
32
+ !value.to_s.empty? && !table_date? && !table_date_range? && !subquery?
33
+ end
34
+
35
+ class << self
36
+ def eval_type(value, options = {})
37
+ new(value, options).eval_type
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,72 @@
1
+ module BB
2
+ module Evaluator
3
+ class Value
4
+ EVALUATE_TYPES = %i(array boolean date null numeric range regexp string subquery time).freeze
5
+
6
+ attr_reader :value, :options
7
+
8
+ def initialize(value, options = {})
9
+ @value = value
10
+ @options = options
11
+ end
12
+
13
+ def eval_type
14
+ eval_types.detect { |type| send("#{type}?") }.tap do |type|
15
+ raise UnevaluableTypeError, "unevaluable type of value: #{value} (#{value.class})" if type.nil?
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def eval_types
22
+ options[:eval_types] || EVALUATE_TYPES
23
+ end
24
+
25
+ def array?
26
+ value.is_a?(Array) && !value.empty?
27
+ end
28
+
29
+ def boolean?
30
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
31
+ end
32
+
33
+ def date?
34
+ value.respond_to?(:strftime) && !value.respond_to?(:hour)
35
+ end
36
+
37
+ def null?
38
+ value.nil?
39
+ end
40
+
41
+ def numeric?
42
+ value.is_a?(Numeric)
43
+ end
44
+
45
+ def range?
46
+ value.is_a?(Range) && !value.size.is_a?(Float)
47
+ end
48
+
49
+ def regexp?
50
+ value.is_a?(Regexp)
51
+ end
52
+
53
+ def string?
54
+ value.is_a?(String) || value.is_a?(Symbol)
55
+ end
56
+
57
+ def subquery?
58
+ value.respond_to?(:to_sql)
59
+ end
60
+
61
+ def time?
62
+ value.respond_to?(:strftime) && value.respond_to?(:hour)
63
+ end
64
+
65
+ class << self
66
+ def eval_type(value, options = {})
67
+ new(value, options).eval_type
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ module BB
2
+ module Evaluator
3
+ autoload :Formula, "b_b/evaluator/formula"
4
+ autoload :Table, "b_b/evaluator/table"
5
+ autoload :Value, "b_b/evaluator/value"
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module BB
2
+ # A general BB exception
3
+ class Error < StandardError; end
4
+
5
+ # Raised when using relation methods without calling required arguments.
6
+ class ArgumentError < Error; end
7
+
8
+ # Raised when behavior is not implemented, usually used in an abstract class.
9
+ class NotImplementedError < Error; end
10
+
11
+ # Raised when using evaluation methods
12
+ class UnevaluableTypeError < Error; end
13
+ end