b_b 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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