qdsl 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b17f51187e9228065276df1168b773eb30edad75
4
- data.tar.gz: cd4bdee9e1610bd6cbaba37f2d8d123d75e99804
3
+ metadata.gz: 3cfa45077d918ab12b58525ffdc20fda82587730
4
+ data.tar.gz: f52f010312f89176572fbfb903353a005a830254
5
5
  SHA512:
6
- metadata.gz: 49da8e1de1dd71759acc1b66324b823c262ced79fe1a0d8de3b7cfc2c656d811445a559bed4de5f7cf4c2cc29af7b52b1724f0871934b6d657cbb0a13c5e8497
7
- data.tar.gz: 742fc9b2c3ef627f1205b3a0baa2fb0e04ece7ad40a77880a2b1c2afa54dff92c7ca14a96b971892632077e70daf872eddcb1cde693ff5296fa2a53908b113c7
6
+ metadata.gz: e85a49678f55f66921aad11e26f73e259674befb8fa970b9734429fdf4b90f7dfef524791181190ecfa05b0b0a15f104a5ea0b4c1778653c42a338b211f64edc
7
+ data.tar.gz: 338110b7b6397848979cdd07323b0b28fbaa3ce86d4da6667276fe621bd12c7a5cbbe82871a06fee87dbfa377a08f6cdbc670caefeb1c53dc062d2b08eb4b1c1
data/lib/and.rb CHANGED
@@ -4,12 +4,16 @@ module Qdsl
4
4
  super 'AND', exprs
5
5
  end
6
6
 
7
+ def to_expression
8
+ self
9
+ end
10
+
7
11
  def and(expr)
8
12
  And.new(@exprs + [expr])
9
13
  end
10
14
 
11
- def or(expr)
12
- Or.new([self, expr])
15
+ def true?
16
+ IsTrue.new(self)
13
17
  end
14
18
  end
15
19
  end
data/lib/boolean.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require_relative 'expression'
2
+
1
3
  module Qdsl
2
4
  class Boolean < Expression
3
5
  def initialize(operator, exprs)
@@ -5,9 +7,10 @@ module Qdsl
5
7
  @exprs = exprs
6
8
  end
7
9
 
8
- def render(context, ids)
10
+ def render_sql(context, ids)
9
11
  expr_results = @exprs.collect { |x| render_operand(context, ids, x) }
10
- expr_results.collect { |x| "(#{x})" }.join(" #{@operator} ")
12
+ parameters = expr_results.inject({}) { |acc, x| acc.merge(x.parameters) }
13
+ SimpleRenderResult.new(expr_results.collect { |x| "(#{x.sql})" }.join(" #{@operator} "), parameters)
11
14
  end
12
15
  end
13
16
  end
data/lib/call.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Qdsl
2
+ class Call
3
+ attr_reader :source, :name
4
+
5
+ def initialize(source, function_name, parameters, name = nil)
6
+ @source = source
7
+ @function_name = function_name
8
+ @parameters = parameters
9
+ @name = name ? name.to_s : nil
10
+ end
11
+
12
+ def as(name)
13
+ Call.new(@source, @function_name, @parameters, name)
14
+ end
15
+
16
+ def exists?
17
+ @parameters.all? { |x| x.exists? }
18
+ end
19
+
20
+ def render_sql(context, id)
21
+ parameter_results = @parameters.collect { |x| x.render_sql(context, id) }
22
+ query_parameters = parameter_results.collect(&:parameters).inject({}) { |acc, x| acc.merge!(x) }
23
+ call = "#{@function_name}(#{parameter_results.collect(&:sql).join(', ')})"
24
+ SimpleRenderResult.new(@name ? "#{call} AS #{@name}" : call, query_parameters)
25
+ end
26
+ end
27
+ end
28
+
data/lib/column.rb CHANGED
@@ -31,8 +31,20 @@ module Qdsl
31
31
  Column.new(@name, @alias_name, source)
32
32
  end
33
33
 
34
- def render(context, id)
35
- @alias_name ? "#{id}.#{@name} AS #{@alias_name}" : "#{id}.#{@name}"
34
+ def exists?
35
+ @source.column_names.include?(@name)
36
+ end
37
+
38
+ def to_expression
39
+ true?
40
+ end
41
+
42
+ def true?
43
+ IsTrue.new(self)
44
+ end
45
+
46
+ def render_sql(context, ids)
47
+ SimpleRenderResult.new(@alias_name ? "#{@source.id}.#{@name} AS #{@alias_name}" : "#{@source.id}.#{@name}", {})
36
48
  end
37
49
 
38
50
  def equals(column)
@@ -1,5 +1,5 @@
1
1
  module Qdsl
2
- class FunkyProxy
2
+ class ColumnProxy
3
3
  def initialize(source)
4
4
  @source = source
5
5
  source.column_names.each do |column|
@@ -12,9 +12,17 @@ module Qdsl
12
12
  end
13
13
 
14
14
  def [](name)
15
- raise unless @source.column_names.include?(name.to_s)
15
+ raise "Unknown column \"#{name}\"" unless @source.column_names.include?(name.to_s)
16
16
  Column.new(name.to_s, nil, @source)
17
17
  end
18
+
19
+ def _call(function_name, *parameters)
20
+ Call.new(@source, function_name, parameters)
21
+ end
22
+
23
+ def _formatcall(format, *parameters)
24
+ FormatCall.new(@source, format, parameters)
25
+ end
18
26
  end
19
27
  end
20
28
 
data/lib/column_set.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Qdsl
2
+ class ColumnSet
3
+ attr_reader :columns, :block
4
+
5
+ def initialize(columns, block)
6
+ @columns = columns.collect { |x| Column[x] }
7
+ @block = block
8
+ end
9
+
10
+ def capture(source, join_sources)
11
+ sources = [source] + join_sources
12
+ columns = @columns.collect { |x| x.with_source(source) } + if @block
13
+ proxies = sources.collect { |x| ColumnProxy.new(x) }
14
+ [*@block.call(Util::fix_block_params(proxies))]
15
+ else
16
+ []
17
+ end
18
+
19
+ undefined_columns = columns.select { |x| !x.exists? }
20
+ raise "One or more undefined columns: #{undefined_columns.collect(&:name).join(', ')}" unless undefined_columns.empty?
21
+ columns
22
+ end
23
+ end
24
+ end
25
+
data/lib/context.rb CHANGED
@@ -1,22 +1,20 @@
1
1
  module Qdsl
2
2
  class Context
3
- attr_reader :parameters
4
-
5
3
  def initialize
6
4
  @id_base = 0
7
- @parameters = {}
5
+ @parameter_id_base = 0
8
6
  end
9
7
 
10
- def id
8
+ def create_id
11
9
  result = "_#{@id_base.to_s.rjust(2, '0')}"
12
10
  @id_base += 1
13
11
  result
14
12
  end
15
13
 
16
- def add_parameter(value)
17
- parameter_id = "_param#{@parameters.size.to_s.rjust(2, '0')}"
18
- @parameters[parameter_id] = value
19
- parameter_id
14
+ def create_parameter_id
15
+ result = "_param#{@parameter_id_base.to_s.rjust(2, '0')}"
16
+ @parameter_id_base += 1
17
+ result
20
18
  end
21
19
  end
22
20
  end
data/lib/equals.rb CHANGED
@@ -5,18 +5,15 @@ module Qdsl
5
5
  @column1 = column1
6
6
  end
7
7
 
8
- def and(expr)
9
- And.new([self, expr])
8
+ def to_expression
9
+ self
10
10
  end
11
11
 
12
- def or(expr)
13
- Or.new([self, expr])
14
- end
15
-
16
- def render(context, ids)
12
+ def render_sql(context, ids)
17
13
  column0_result = render_operand(context, ids, @column0)
18
14
  column1_result = render_operand(context, ids, @column1)
19
- "#{column0_result} = #{column1_result}"
15
+ parameters = column0_result.parameters.merge(column1_result.parameters)
16
+ SimpleRenderResult.new("#{column0_result.sql} = #{column1_result.sql}", parameters)
20
17
  end
21
18
  end
22
19
  end
data/lib/expression.rb CHANGED
@@ -1,19 +1,27 @@
1
1
  module Qdsl
2
2
  class Expression
3
+ def and(expr)
4
+ And.new([self, expr])
5
+ end
6
+
7
+ def or(expr)
8
+ Or.new([self, expr])
9
+ end
10
+
3
11
  protected
4
12
 
5
13
  def render_operand(context, ids, operand)
6
14
  if operand.is_a?(String)
7
- parameter_id = context.add_parameter(operand)
8
- ":#{parameter_id}"
15
+ parameter_id = context.create_parameter_id
16
+ SimpleRenderResult.new(":#{parameter_id}", {parameter_id => operand})
9
17
  elsif operand.is_a?(TrueClass)
10
- 'TRUE'
18
+ SimpleRenderResult.new('TRUE', {})
11
19
  elsif operand.is_a?(FalseClass)
12
- 'FALSE'
20
+ SimpleRenderResult.new('FALSE', {})
13
21
  elsif operand.is_a?(Expression)
14
- operand.render(context, ids)
22
+ operand.render_sql(context, ids)
15
23
  else
16
- operand.render(context, ids[operand.source])
24
+ operand.render_sql(context, ids[operand.source])
17
25
  end
18
26
  end
19
27
  end
@@ -0,0 +1,37 @@
1
+ module Qdsl
2
+ class FormatCall
3
+ attr_reader :source, :name
4
+
5
+ def initialize(source, format, parameters, name = nil)
6
+ @source = source
7
+ @format = format
8
+ @parameters = parameters
9
+ @name = name.to_s
10
+ end
11
+
12
+ def as(name)
13
+ FormatCall.new(@source, @format, @parameters, name)
14
+ end
15
+
16
+ def exists?
17
+ @parameters.all? { |x| x.exists? }
18
+ end
19
+
20
+ def render_sql(context, id)
21
+ parameter_results = @parameters.collect { |x| x.render_sql(context, id) }
22
+ query_parameters = parameter_results.collect(&:parameters).inject({}) { |acc, x| acc.merge!(x) }
23
+ call = do_format(@format, parameter_results.collect(&:sql))
24
+ SimpleRenderResult.new(@name ? "#{call} AS #{@name}" : call, query_parameters)
25
+ end
26
+
27
+ private
28
+
29
+ def do_format(format, parameters)
30
+ format.gsub /%(\d+)/ do |m, x|
31
+ index = Integer(Regexp.last_match[1]) - 1
32
+ parameters[index]
33
+ end
34
+ end
35
+ end
36
+ end
37
+
data/lib/inner_join.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Qdsl
2
2
  class InnerJoin
3
- attr_reader :source, :predicate
3
+ attr_reader :source, :predicate_block
4
4
 
5
- def initialize(source, predicate)
5
+ def initialize(source, predicate_block)
6
6
  @source = source
7
- @predicate = predicate
7
+ @predicate_block = predicate_block
8
8
  end
9
9
  end
10
10
  end
@@ -1,13 +1,12 @@
1
1
  module Qdsl
2
2
  class InnerJoinBuilder
3
- def initialize(query_builder, source)
4
- @query_builder = query_builder
3
+ def initialize(select, source)
4
+ @select = select
5
5
  @source = source
6
6
  end
7
7
 
8
- def on
9
- predicate = yield(FunkyProxy.new(@query_builder.source), FunkyProxy.new(@source))
10
- @query_builder.add_inner_join(@source, predicate)
8
+ def on(&predicate_block)
9
+ @select.add_inner_join(@source, predicate_block)
11
10
  end
12
11
  end
13
12
  end
data/lib/is_true.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Qdsl
2
+ class IsTrue < Expression
3
+ def initialize(column)
4
+ @column = column
5
+ end
6
+
7
+ def to_expression
8
+ self
9
+ end
10
+
11
+ def render_sql(context, ids)
12
+ column_result = render_operand(context, ids, @column)
13
+ column_result
14
+ end
15
+ end
16
+ end
17
+
data/lib/or.rb CHANGED
@@ -1,16 +1,22 @@
1
+ require_relative 'boolean'
2
+
1
3
  module Qdsl
2
4
  class Or < Boolean
3
5
  def initialize(exprs)
4
6
  super 'OR', exprs
5
7
  end
6
8
 
7
- def and(expr)
8
- And.new([self, expr])
9
+ def to_expression
10
+ self
9
11
  end
10
12
 
11
13
  def or(expr)
12
14
  Or.new(@exprs + [expr])
13
15
  end
16
+
17
+ def true?
18
+ IsTrue.new(self)
19
+ end
14
20
  end
15
21
  end
16
22
 
data/lib/qdsl/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Qdsl
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
4
4
 
data/lib/qdsl.rb CHANGED
@@ -1,27 +1,13 @@
1
1
  require 'qdsl/version'
2
+ require 'byebug'
2
3
 
3
- %w{
4
- expression
5
- boolean
6
- and
7
- column
8
- context
9
- equals
10
- funky_proxy
11
- inner_join
12
- inner_join_builder
13
- or
14
- qdsl
15
- select
16
- table
17
- table_query
18
- }.each do |file_name|
19
- require_relative file_name
4
+ Dir.glob("#{File.dirname(__FILE__)}/*.rb").each do |file_name|
5
+ require file_name
20
6
  end
21
7
 
22
8
  module Qdsl
23
9
  def self.select(*columns, &block)
24
- Select.new(columns.collect { |x| Column[x] }, &block)
10
+ Select.new(ColumnSet.new(columns, block))
25
11
  end
26
12
  end
27
13
 
@@ -0,0 +1,13 @@
1
+ module Qdsl
2
+ class RenderResult < SimpleRenderResult
3
+ attr_reader :id, :column_names, :parameters
4
+
5
+ def initialize(id, sql, column_names, parameters)
6
+ super sql, parameters
7
+ @id = id
8
+ @column_names = column_names
9
+ @parameters = parameters
10
+ end
11
+ end
12
+ end
13
+
data/lib/select.rb CHANGED
@@ -1,94 +1,127 @@
1
1
  module Qdsl
2
- class SelectQuery
3
- attr_reader :column_names
4
-
5
- def initialize(select, column_names)
6
- @select = select
7
- @column_names = column_names
8
- end
9
-
10
- def render(context, depth, id)
11
- select_result = @select.render(context, depth)
12
- "(\n#{select_result}) AS #{id}"
13
- end
14
- end
15
-
16
2
  class Select
17
3
  attr_reader :source
18
4
 
19
- def initialize(columns, &block)
20
- @columns = columns
21
- @block = block
5
+ def initialize(column_set)
6
+ @column_set = column_set
22
7
  @inner_joins = []
23
8
  end
24
9
 
25
10
  def from(source)
26
- @source = source.create_query
11
+ @source = source
27
12
  self
28
13
  end
29
14
 
30
- def create_query
31
- # Clone columns?
32
- column_names = @columns.collect(&:name)
33
- SelectQuery.new(self, column_names)
34
- end
15
+ def where(&predicate_block)
16
+ @where_predicate_block = predicate_block
17
+ self
18
+ end
19
+
20
+ def order_by(*columns, &block)
21
+ @order_by_column_set = ColumnSet.new(columns, block)
22
+ self
23
+ end
35
24
 
36
- def where
37
- sources = [@source] + @inner_joins.collect(&:source)
38
- proxies = sources.collect { |x| FunkyProxy.new(x) }
39
- @where = yield(proxies.size == 1 ? proxies.first : proxies)
25
+ def limit(count)
26
+ @count = count
40
27
  self
41
28
  end
42
29
 
43
30
  def inner_join(source)
44
- InnerJoinBuilder.new(self, source.create_query)
31
+ InnerJoinBuilder.new(self, source)
32
+ end
33
+
34
+ def create_query
35
+ SelectQuery.new(self, @column_set)
45
36
  end
46
37
 
47
- def add_inner_join(source, predicate)
48
- @inner_joins << InnerJoin.new(source, predicate)
38
+ def add_inner_join(source, predicate_block)
39
+ @inner_joins << InnerJoin.new(source, predicate_block)
49
40
  self
50
41
  end
51
42
 
52
- def render(context = nil, depth = 0)
53
- context ||= Context.new
43
+ def render(context = nil, depth = 0, id = nil)
44
+ raise 'No FROM clause was specified' unless @source
54
45
 
55
46
  indent = ' ' * depth
56
47
 
57
- unknown_columns = @columns.select { |x| !@source.column_names.include?(x.name) }
58
- raise "One or more unknown columns: #{unknown_columns.collect(&:name).join(', ')}" unless unknown_columns.empty?
48
+ context ||= Context.new
59
49
 
60
- columns = @columns.collect { |x| x.with_source(@source) }
50
+ parameters = {}
61
51
 
62
- sources = [@source] + @inner_joins.collect(&:source)
52
+ source_query = @source.create_query
53
+ source_query_id = context.create_id
54
+ source_result = source_query.render(context, depth + 1, source_query_id)
63
55
 
64
- extra_columns = if @block
65
- proxies = sources.collect { |x| FunkyProxy.new(x) }
66
- [*@block.call(proxies.size == 1 ? proxies.first : proxies)]
67
- else
68
- []
56
+ inner_join_fragments = []
57
+ inner_join_results = []
58
+ @inner_joins.each do |inner_join|
59
+ inner_join_query = inner_join.source.create_query
60
+ inner_join_query_id = context.create_id
61
+ inner_join_result = inner_join_query.render(context, depth + 1, inner_join_query_id)
62
+ inner_join_results << inner_join_result
63
+ x = ColumnProxy.new(source_result)
64
+ y = ColumnProxy.new(inner_join_result)
65
+ predicate = inner_join.predicate_block.call(x, y)
66
+
67
+ # Should just use id attribute instead of passing dictionary!
68
+ bah = {
69
+ source_result => source_result.id,
70
+ inner_join_result => inner_join_result.id,
71
+ }
72
+
73
+ predicate_result = predicate.render_sql(context, bah)
74
+
75
+ inner_join_fragments << "#{indent}INNER JOIN #{inner_join_result.sql}"
76
+ inner_join_fragments << "#{indent}ON #{predicate_result.sql}\n"
69
77
  end
70
78
 
71
- ids = sources.inject({}) { |acc, x| acc[x] = context.id; acc }
79
+ columns = @column_set.capture(source_result, inner_join_results)
72
80
 
73
- column_results = (columns + extra_columns).collect { |x| x.render(context, ids[x.source]) }
81
+ column_results = columns.collect { |x| x.render_sql(context, x.source.id) }
74
82
 
75
83
  fragments = []
76
84
 
77
- fragments << "#{indent}SELECT #{column_results.join(', ')}\n"
78
- fragments << "#{indent}FROM #{@source.render(context, depth + 1, ids[@source])}\n"
85
+ fragments << "#{indent}SELECT\n"
86
+ column_results.each_with_index do |column_result, index|
87
+ fragments << "#{indent} #{column_result.sql}#{index < column_results.size - 1 ? ',' : ''}\n"
88
+ end
79
89
 
80
- @inner_joins.each do |inner_join|
81
- predicate_result = inner_join.predicate.render(context, ids)
82
- fragments << "#{indent}INNER JOIN #{inner_join.source.render(context, depth + 1, ids[inner_join.source])}\n"
83
- fragments << "#{indent}ON #{predicate_result}\n"
90
+ fragments << "#{indent}FROM #{source_result.sql}"
91
+
92
+ fragments += inner_join_fragments
93
+
94
+ if @where_predicate_block
95
+ blah = [source_result] + inner_join_results
96
+ proxies = blah.collect { |x| ColumnProxy.new(x) }
97
+ where_predicate = @where_predicate_block.call(Util::fix_block_params(proxies))
98
+
99
+ # Should just use id attribute instead of passing dictionary!
100
+ bah = blah.inject({}) { |acc, x| acc[x] = x.id; acc }
101
+
102
+ where_predicate_result = where_predicate.render_sql(context, bah)
103
+ parameters.merge!(where_predicate_result.parameters)
104
+
105
+ fragments << "#{indent}WHERE #{where_predicate_result.sql}\n"
106
+ end
107
+
108
+ if @order_by_column_set
109
+ order_by_columns = @order_by_column_set.capture(source_result, inner_join_results)
110
+ order_by_column_results = order_by_columns.collect { |x| x.render_sql(context, x.source.id) }
111
+ fragments << "#{indent}ORDER BY #{order_by_column_results.collect(&:sql).join(', ')}\n"
84
112
  end
85
113
 
86
- if @where
87
- predicate_result = @where.render(context, ids)
88
- fragments << "#{indent}WHERE #{predicate_result}"
114
+ if @count
115
+ fragments << "LIMIT #{@count}"
89
116
  end
90
117
 
91
- fragments.join
118
+ sql = fragments.join
119
+ RenderResult.new(
120
+ id,
121
+ sql,
122
+ columns.collect(&:name),
123
+ parameters
124
+ )
92
125
  end
93
126
  end
94
127
  end
@@ -0,0 +1,30 @@
1
+ module Qdsl
2
+ class SelectQuery
3
+ def initialize(select, column_set)
4
+ @select = select
5
+ @column_set = column_set
6
+ end
7
+
8
+ def render(context, depth, id)
9
+ indent = ' ' * depth
10
+
11
+ columns = @column_set.capture(@select.source, [])
12
+
13
+ select_result = @select.render(context, depth)
14
+
15
+ fragments = []
16
+ fragments << "(\n"
17
+ fragments << select_result.sql
18
+ fragments << ") AS #{id}\n"
19
+
20
+ sql = fragments.join
21
+ RenderResult.new(
22
+ id,
23
+ sql,
24
+ columns.collect(&:name),
25
+ {}
26
+ )
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,11 @@
1
+ module Qdsl
2
+ class SimpleRenderResult
3
+ attr_reader :sql, :parameters
4
+
5
+ def initialize(sql, parameters)
6
+ @sql = sql
7
+ @parameters = parameters
8
+ end
9
+ end
10
+ end
11
+
data/lib/table_query.rb CHANGED
@@ -8,7 +8,7 @@ module Qdsl
8
8
  end
9
9
 
10
10
  def render(context, depth, id)
11
- "#{@name} AS #{id}"
11
+ RenderResult.new(id, "#{@name} AS #{id}\n", @column_names, {})
12
12
  end
13
13
  end
14
14
  end
data/lib/util.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Qdsl
2
+ module Util
3
+ def self.fix_block_params(params)
4
+ params.size == 1 ? params.first : params
5
+ end
6
+ end
7
+ end
8
+