qdsl 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+