ronin-sql 0.2.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/.document +4 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -0
  5. data/COPYING.txt +623 -288
  6. data/{History.txt → ChangeLog.md} +33 -35
  7. data/Gemfile +25 -0
  8. data/README.md +110 -0
  9. data/Rakefile +30 -20
  10. data/bin/ronin-sql +18 -5
  11. data/gemspec.yml +16 -0
  12. data/lib/ronin/formatting/extensions/sql.rb +4 -3
  13. data/lib/ronin/formatting/extensions/sql/string.rb +83 -10
  14. data/lib/ronin/formatting/sql.rb +4 -3
  15. data/lib/ronin/sql.rb +5 -12
  16. data/lib/ronin/{code/sql/create_index.rb → sql/binary_expr.rb} +25 -18
  17. data/lib/ronin/sql/clause.rb +72 -0
  18. data/lib/ronin/sql/clauses.rb +297 -0
  19. data/lib/ronin/sql/emittable.rb +84 -0
  20. data/lib/ronin/sql/emitter.rb +375 -0
  21. data/lib/ronin/sql/field.rb +106 -0
  22. data/lib/ronin/{code/sql/as.rb → sql/fields.rb} +36 -17
  23. data/lib/ronin/{code/sql/binary_expr.rb → sql/function.rb} +27 -27
  24. data/lib/ronin/sql/functions.rb +989 -0
  25. data/lib/ronin/sql/injection.rb +125 -157
  26. data/lib/ronin/{code/sql/default_values_clause.rb → sql/literal.rb} +13 -11
  27. data/lib/ronin/sql/literals.rb +72 -0
  28. data/lib/ronin/sql/operators.rb +332 -0
  29. data/lib/ronin/sql/sql.rb +86 -0
  30. data/lib/ronin/sql/statement.rb +70 -0
  31. data/lib/ronin/sql/statement_list.rb +110 -0
  32. data/lib/ronin/sql/statements.rb +115 -0
  33. data/lib/ronin/{code/sql/desc.rb → sql/unary_expr.rb} +11 -11
  34. data/lib/ronin/sql/version.rb +5 -4
  35. data/ronin-sql.gemspec +61 -0
  36. data/spec/formatting/sql/string_spec.rb +172 -0
  37. data/spec/spec_helper.rb +1 -4
  38. data/spec/sql/binary_expr.rb +5 -0
  39. data/spec/sql/binary_expr_examples.rb +25 -0
  40. data/spec/sql/clause_examples.rb +43 -0
  41. data/spec/sql/clause_spec.rb +31 -0
  42. data/spec/sql/clauses_spec.rb +43 -0
  43. data/spec/sql/emittable_spec.rb +41 -0
  44. data/spec/sql/emitter_spec.rb +472 -0
  45. data/spec/sql/field_spec.rb +103 -0
  46. data/spec/sql/fields_spec.rb +40 -0
  47. data/spec/sql/function_examples.rb +30 -0
  48. data/spec/sql/function_spec.rb +25 -0
  49. data/spec/sql/functions_spec.rb +110 -0
  50. data/spec/sql/injection_spec.rb +233 -0
  51. data/spec/sql/literal_spec.rb +5 -0
  52. data/spec/sql/literals_spec.rb +46 -0
  53. data/spec/sql/operators_spec.rb +44 -0
  54. data/spec/sql/sql_spec.rb +18 -0
  55. data/spec/sql/statement_examples.rb +39 -0
  56. data/spec/sql/statement_list_spec.rb +48 -0
  57. data/spec/sql/statement_sql.rb +38 -0
  58. data/spec/sql/statements_spec.rb +22 -0
  59. data/spec/sql/unary_expr.rb +5 -0
  60. data/spec/sql/unary_expr_examples.rb +20 -0
  61. metadata +116 -217
  62. data.tar.gz.sig +0 -0
  63. data/Manifest.txt +0 -108
  64. data/README.txt +0 -112
  65. data/lib/ronin/code/sql.rb +0 -22
  66. data/lib/ronin/code/sql/add_column_clause.rb +0 -42
  67. data/lib/ronin/code/sql/alter_table.rb +0 -52
  68. data/lib/ronin/code/sql/asc.rb +0 -36
  69. data/lib/ronin/code/sql/between.rb +0 -66
  70. data/lib/ronin/code/sql/clause.rb +0 -35
  71. data/lib/ronin/code/sql/code.rb +0 -35
  72. data/lib/ronin/code/sql/common_dialect.rb +0 -66
  73. data/lib/ronin/code/sql/create.rb +0 -74
  74. data/lib/ronin/code/sql/create_table.rb +0 -44
  75. data/lib/ronin/code/sql/create_view.rb +0 -41
  76. data/lib/ronin/code/sql/delete.rb +0 -52
  77. data/lib/ronin/code/sql/dialect.rb +0 -282
  78. data/lib/ronin/code/sql/drop.rb +0 -55
  79. data/lib/ronin/code/sql/drop_index.rb +0 -41
  80. data/lib/ronin/code/sql/drop_table.rb +0 -41
  81. data/lib/ronin/code/sql/drop_view.rb +0 -41
  82. data/lib/ronin/code/sql/emittable.rb +0 -100
  83. data/lib/ronin/code/sql/exceptions.rb +0 -24
  84. data/lib/ronin/code/sql/exceptions/unknown_clause.rb +0 -29
  85. data/lib/ronin/code/sql/exceptions/unknown_dialect.rb +0 -29
  86. data/lib/ronin/code/sql/exceptions/unknown_statement.rb +0 -29
  87. data/lib/ronin/code/sql/expr.rb +0 -102
  88. data/lib/ronin/code/sql/field.rb +0 -101
  89. data/lib/ronin/code/sql/fields_clause.rb +0 -46
  90. data/lib/ronin/code/sql/from_clause.rb +0 -42
  91. data/lib/ronin/code/sql/function.rb +0 -53
  92. data/lib/ronin/code/sql/group_by_clause.rb +0 -46
  93. data/lib/ronin/code/sql/having_clause.rb +0 -46
  94. data/lib/ronin/code/sql/in.rb +0 -47
  95. data/lib/ronin/code/sql/injected_statement.rb +0 -100
  96. data/lib/ronin/code/sql/injection.rb +0 -203
  97. data/lib/ronin/code/sql/insert.rb +0 -54
  98. data/lib/ronin/code/sql/intersect_clause.rb +0 -42
  99. data/lib/ronin/code/sql/join_clause.rb +0 -123
  100. data/lib/ronin/code/sql/like.rb +0 -73
  101. data/lib/ronin/code/sql/limit_clause.rb +0 -42
  102. data/lib/ronin/code/sql/modifier.rb +0 -48
  103. data/lib/ronin/code/sql/offset_clause.rb +0 -42
  104. data/lib/ronin/code/sql/on_clause.rb +0 -55
  105. data/lib/ronin/code/sql/order_by_clause.rb +0 -42
  106. data/lib/ronin/code/sql/program.rb +0 -225
  107. data/lib/ronin/code/sql/rename_to_clause.rb +0 -42
  108. data/lib/ronin/code/sql/replace.rb +0 -54
  109. data/lib/ronin/code/sql/select.rb +0 -103
  110. data/lib/ronin/code/sql/set_clause.rb +0 -42
  111. data/lib/ronin/code/sql/statement.rb +0 -180
  112. data/lib/ronin/code/sql/token.rb +0 -62
  113. data/lib/ronin/code/sql/unary_expr.rb +0 -47
  114. data/lib/ronin/code/sql/union_all_clause.rb +0 -42
  115. data/lib/ronin/code/sql/union_clause.rb +0 -42
  116. data/lib/ronin/code/sql/update.rb +0 -52
  117. data/lib/ronin/code/sql/values_clause.rb +0 -46
  118. data/lib/ronin/code/sql/where_clause.rb +0 -42
  119. data/lib/ronin/sql/error.rb +0 -26
  120. data/lib/ronin/sql/error/error.rb +0 -62
  121. data/lib/ronin/sql/error/extensions.rb +0 -22
  122. data/lib/ronin/sql/error/extensions/string.rb +0 -77
  123. data/lib/ronin/sql/error/message.rb +0 -62
  124. data/lib/ronin/sql/error/pattern.rb +0 -104
  125. data/lib/ronin/sql/error/patterns.rb +0 -99
  126. data/lib/ronin/sql/extensions.rb +0 -22
  127. data/lib/ronin/sql/extensions/uri.rb +0 -22
  128. data/lib/ronin/sql/extensions/uri/http.rb +0 -107
  129. data/spec/code/sql/common_dialect_spec.rb +0 -205
  130. data/spec/code/sql/create_examples.rb +0 -19
  131. data/spec/code/sql/create_index_spec.rb +0 -25
  132. data/spec/code/sql/create_table_spec.rb +0 -27
  133. data/spec/code/sql/create_view_spec.rb +0 -16
  134. data/spec/code/sql/delete_spec.rb +0 -14
  135. data/spec/code/sql/drop_examples.rb +0 -10
  136. data/spec/code/sql/drop_index_spec.rb +0 -16
  137. data/spec/code/sql/drop_table_spec.rb +0 -16
  138. data/spec/code/sql/drop_view_spec.rb +0 -16
  139. data/spec/code/sql/has_default_values_clause_examples.rb +0 -10
  140. data/spec/code/sql/has_fields_clause_examples.rb +0 -15
  141. data/spec/code/sql/has_from_clause_examples.rb +0 -13
  142. data/spec/code/sql/has_values_clause_examples.rb +0 -15
  143. data/spec/code/sql/has_where_clause_examples.rb +0 -15
  144. data/spec/code/sql/insert_spec.rb +0 -21
  145. data/spec/code/sql/replace_spec.rb +0 -21
  146. data/spec/code/sql/select_spec.rb +0 -105
  147. data/spec/code/sql/update_spec.rb +0 -26
  148. data/spec/helpers/code.rb +0 -14
  149. data/spec/sql/error_spec.rb +0 -24
  150. data/spec/sql/extensions/uri/http_spec.rb +0 -34
  151. data/spec/sql_spec.rb +0 -9
  152. data/tasks/spec.rb +0 -10
  153. data/tasks/yard.rb +0 -13
  154. metadata.gz.sig +0 -0
@@ -0,0 +1,172 @@
1
+ require 'spec_helper'
2
+ require 'ronin/formatting/extensions/sql/string'
3
+
4
+ describe String do
5
+ it "should provide the #sql_escape method" do
6
+ subject.should respond_to(:sql_escape)
7
+ end
8
+
9
+ it "should provide the #sql_encode method" do
10
+ subject.should respond_to(:sql_encode)
11
+ end
12
+
13
+ it "should provide the #sql_decode method" do
14
+ subject.should respond_to(:sql_decode)
15
+ end
16
+
17
+ describe "#sql_escape" do
18
+ subject { "hello" }
19
+
20
+ context "with :single" do
21
+ it "should wrap the String in single-quotes" do
22
+ subject.sql_escape(:single).should == "'hello'"
23
+ end
24
+
25
+ context "when the String already contains single-quotes" do
26
+ subject { "O'Brian" }
27
+
28
+ it "should escape existing single-quotes" do
29
+ subject.sql_escape(:single).should == "'O''Brian'"
30
+ end
31
+ end
32
+ end
33
+
34
+ context "with :double" do
35
+ it "should wrap the String in double-quotes" do
36
+ subject.sql_escape(:double).should == '"hello"'
37
+ end
38
+
39
+ context "when the String already contains double-quotes" do
40
+ subject { 'the "thing"' }
41
+
42
+ it "should escape existing double-quotes" do
43
+ subject.sql_escape(:double).should == '"the ""thing"""'
44
+ end
45
+ end
46
+ end
47
+
48
+ context "with :tick" do
49
+ it "should wrap the String in tick-mark quotes" do
50
+ subject.sql_escape(:tick).should == "`hello`"
51
+ end
52
+
53
+ context "when the String already contains tick-marks" do
54
+ subject { "the `thing`" }
55
+
56
+ it "should escape existing tick-mark quotes" do
57
+ subject.sql_escape(:tick).should == '`the ``thing```'
58
+ end
59
+ end
60
+ end
61
+
62
+ context "with no arguments" do
63
+ it "should default quote to :single" do
64
+ subject.sql_escape.should == subject.sql_escape(:single)
65
+ end
66
+ end
67
+
68
+ context "otherwise" do
69
+ it "should raise an ArgumentError" do
70
+ lambda { subject.sql_escape(:foo) }.should raise_error(ArgumentError)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#sql_unescape" do
76
+ context "when the String is single-quoted" do
77
+ subject { "'hello'" }
78
+
79
+ it "should remove leading and tailing single-quotes" do
80
+ subject.sql_unescape.should == "hello"
81
+ end
82
+
83
+ context "when the String contains escaped single-quotes" do
84
+ subject { "'O''Brian'" }
85
+
86
+ it "should unescape the single-quotes" do
87
+ subject.sql_unescape.should == "O'Brian"
88
+ end
89
+ end
90
+ end
91
+
92
+ context "when the String is double-quoted" do
93
+ subject { '"hello"' }
94
+
95
+ it "should remove leading and tailing double-quotes" do
96
+ subject.sql_unescape.should == 'hello'
97
+ end
98
+
99
+ context "when the String contains escaped double-quotes" do
100
+ subject { '"the ""thing"""' }
101
+
102
+ it "should unescape the double-quotes" do
103
+ subject.sql_unescape.should == 'the "thing"'
104
+ end
105
+ end
106
+ end
107
+
108
+ context "when the String is tick-mark quoted" do
109
+ subject { '`hello`' }
110
+
111
+ it "should remove leading and tailing tick-mark quotes" do
112
+ subject.sql_unescape.should == 'hello'
113
+ end
114
+
115
+ context "when the String contains escaped tick-mark quotes" do
116
+ subject { '`the ``thing```' }
117
+
118
+ it "should unescape the tick-mark quotes" do
119
+ subject.sql_unescape.should == 'the `thing`'
120
+ end
121
+ end
122
+ end
123
+
124
+ context "when the String is not quoted" do
125
+ subject { "hello" }
126
+
127
+ it "should raise an exception" do
128
+ lambda { subject.sql_unescape }.should raise_error(TypeError)
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "#sql_encode" do
134
+ subject { "/etc/passwd" }
135
+
136
+ let(:encoded_string) { '0x2f6574632f706173737764' }
137
+
138
+ it "should be able to be SQL-hex encoded" do
139
+ subject.sql_encode.should == encoded_string
140
+ end
141
+
142
+ it "should return an empty String if empty" do
143
+ ''.sql_encode.should == ''
144
+ end
145
+ end
146
+
147
+ describe "#sql_decode" do
148
+ subject { '2f6574632f706173737764' }
149
+
150
+ let(:decoded_string) { '/etc/passwd' }
151
+
152
+ it "should be able to be SQL-hex decoded" do
153
+ subject.sql_decode.should == decoded_string
154
+ end
155
+
156
+ context "with upper-case hexadecimal" do
157
+ subject { '2F6574632F706173737764' }
158
+
159
+ it "should be able to be SQL-hex decoded" do
160
+ subject.sql_decode.should == decoded_string
161
+ end
162
+ end
163
+
164
+ context "when the String is a SQL escaped string" do
165
+ subject { "'Conan O''Brian'" }
166
+
167
+ it "should unescape the SQL String" do
168
+ subject.sql_decode.should == "Conan O'Brian"
169
+ end
170
+ end
171
+ end
172
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,4 @@
1
- require 'rubygems'
2
- gem 'rspec', '>=1.2.8'
3
- require 'spec'
4
-
1
+ require 'rspec'
5
2
  require 'ronin/sql/version'
6
3
 
7
4
  include Ronin
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+ require 'ronin/sql/binary_expr'
3
+
4
+ describe SQL::BinaryExpr do
5
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'ronin/sql/binary_expr'
3
+
4
+ shared_examples_for "BinaryExpr" do |method,operator=method|
5
+ describe "##{method}" do
6
+ let(:operand) { 1 }
7
+ let(:expr) { subject.send(method,operand) }
8
+
9
+ it "should be a BinaryExpr" do
10
+ expr.should be_kind_of(SQL::BinaryExpr)
11
+ end
12
+
13
+ it "should set the left-hand side operand" do
14
+ expr.left.should == subject
15
+ end
16
+
17
+ it "should have a '#{operator}' operator" do
18
+ expr.operator.should == operator
19
+ end
20
+
21
+ it "should set the right-hand side operand" do
22
+ expr.right.should == operand
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "Clause" do |method,keyword,argument_or_block=nil|
4
+ describe "##{method}" do
5
+ case argument_or_block
6
+ when Proc
7
+ before { subject.send(method,&argument_or_block) }
8
+ when Array
9
+ let(:arguments) { argument_or_block }
10
+
11
+ before { subject.send(method,*arguments) }
12
+ when NilClass
13
+ before { subject.send(method) }
14
+ else
15
+ let(:argument) { argument_or_block }
16
+
17
+ before { subject.send(method,argument) }
18
+ end
19
+
20
+ it "should add a #{keyword} clause" do
21
+ clause.keyword.should == keyword
22
+ end
23
+
24
+ case argument_or_block
25
+ when Proc
26
+ it "should accept a block" do
27
+ clause.argument.should_not be_nil
28
+ end
29
+ when NilClass
30
+ it "should not have an argument" do
31
+ clause.argument.should be_nil
32
+ end
33
+ when Array
34
+ it "should accept an argument" do
35
+ clause.argument.should == arguments
36
+ end
37
+ else
38
+ it "should accept an argument" do
39
+ clause.argument.should == argument
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'ronin/sql/clause'
3
+
4
+ describe SQL::Clause do
5
+ describe "#initialize" do
6
+ context "when given an argument" do
7
+ let(:argument) { 1 }
8
+
9
+ subject { described_class.new(:CLAUSE,argument) }
10
+
11
+ it "should set the argument" do
12
+ subject.argument.should == argument
13
+ end
14
+ end
15
+
16
+ context "when given a block" do
17
+ subject do
18
+ described_class.new(:CLAUSE) { 1 }
19
+ end
20
+
21
+ it "should use the return value as the argument" do
22
+ subject.argument.should == 1
23
+ end
24
+
25
+ context "that accepts an argument" do
26
+ it "should yield itself" do
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'sql/clause_examples'
3
+ require 'ronin/sql/clause'
4
+ require 'ronin/sql/clauses'
5
+
6
+ describe SQL::Clauses do
7
+ subject { Object.new.extend(described_class) }
8
+
9
+ let(:clause) { subject.clauses.last }
10
+ its(:clauses) { should be_empty }
11
+
12
+ describe "#clause" do
13
+ let(:keyword) { :EXEC }
14
+
15
+ before { subject.clause(keyword) }
16
+
17
+ it "should add an arbitrary clause" do
18
+ clause.keyword.should == keyword
19
+ end
20
+ end
21
+
22
+ include_examples "Clause", :from, :FROM, :table
23
+ include_examples "Clause", :into, :INTO, :table
24
+ include_examples "Clause", :where, :WHERE, proc { id == 1 }
25
+ include_examples "Clause", :join, :JOIN, :table
26
+ include_examples "Clause", :inner_join, [:INNER, :JOIN], :table
27
+ include_examples "Clause", :left_join, [:LEFT, :JOIN], :table
28
+ include_examples "Clause", :right_join, [:RIGHT, :JOIN], :table
29
+ include_examples "Clause", :full_join, [:FULL, :JOIN], :table
30
+ include_examples "Clause", :on, :ON, proc { id == 1 }
31
+ include_examples "Clause", :union, :UNION, proc { select(:*).from(:table) }
32
+ include_examples "Clause", :group_by, [:GROUP, :BY], [:column1, :column2]
33
+ include_examples "Clause", :having, :HAVING, proc { max(priv) > 100 }
34
+ include_examples "Clause", :limit, :LIMIT, 100
35
+ include_examples "Clause", :offset, :OFFSET, 20
36
+ include_examples "Clause", :top, :TOP, 50
37
+ include_examples "Clause", :into, :INTO, :table
38
+ include_examples "Clause", :values, :VALUES, [1,2,3,4]
39
+ include_examples "Clause", :default_values, [:DEFAULT, :VALUES]
40
+ include_examples "Clause", :set, :SET, {x: 1, y: 2}
41
+ include_examples "Clause", :indexed_by, [:INDEXED, :BY], :index_name
42
+ include_examples "Clause", :not_indexed, [:NOT, :INDEXED]
43
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'ronin/sql/emittable'
3
+ require 'ronin/sql/literal'
4
+
5
+ describe SQL::Emittable do
6
+ subject { SQL::Literal.new('hello') }
7
+
8
+ describe "#emitter" do
9
+ it "should return an SQL::Emitter" do
10
+ subject.emitter.should be_kind_of(SQL::Emitter)
11
+ end
12
+
13
+ it "should accept Emitter options" do
14
+ subject.emitter(:case => :lower).case.should == :lower
15
+ end
16
+ end
17
+
18
+ describe "#to_sql" do
19
+ it "should emit the object" do
20
+ subject.to_sql.should == "'hello'"
21
+ end
22
+
23
+ context "when given options" do
24
+ it "should pass them to #emitter" do
25
+ subject.to_sql(quotes: :double).should == '"hello"'
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#to_s" do
31
+ it "should call #to_sql with no arguments" do
32
+ subject.to_s.should == subject.to_sql
33
+ end
34
+ end
35
+
36
+ describe "#inspect" do
37
+ it "should call #to_sql with no arguments" do
38
+ subject.inspect.should include(subject.to_sql)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,472 @@
1
+ require 'spec_helper'
2
+ require 'ronin/sql/literal'
3
+ require 'ronin/sql/unary_expr'
4
+ require 'ronin/sql/binary_expr'
5
+ require 'ronin/sql/field'
6
+ require 'ronin/sql/statement'
7
+ require 'ronin/sql/statement_list'
8
+ require 'ronin/sql/emitter'
9
+
10
+ describe SQL::Emitter do
11
+ describe "#initialize" do
12
+ context "without options" do
13
+ its(:space) { should == ' ' }
14
+ its(:quotes) { should == :single }
15
+ end
16
+ end
17
+
18
+ describe "#emit_keyword" do
19
+ context "when passed an Array of Symbols" do
20
+ let(:keywords) { [:DROP, :TABLE] }
21
+
22
+ it "should join the keywords" do
23
+ subject.emit_keyword(keywords).should == "DROP TABLE"
24
+ end
25
+
26
+ context "when :space is set" do
27
+ subject { described_class.new(space: '/**/') }
28
+
29
+ it "should join the keywords" do
30
+ subject.emit_keyword(keywords).should == "DROP/**/TABLE"
31
+ end
32
+ end
33
+ end
34
+
35
+ context "when case is :upper" do
36
+ let(:keyword) { :select }
37
+
38
+ subject { described_class.new(case: :upper) }
39
+
40
+ it "should upcase the keyword" do
41
+ subject.emit_keyword(keyword).should == 'SELECT'
42
+ end
43
+ end
44
+
45
+ context "when case is :lower" do
46
+ let(:keyword) { :SELECT }
47
+
48
+ subject { described_class.new(case: :lower) }
49
+
50
+ it "should upcase the keyword" do
51
+ subject.emit_keyword(keyword).should == 'select'
52
+ end
53
+ end
54
+
55
+ context "when case is :random" do
56
+ let(:keyword) { :select }
57
+
58
+ subject { described_class.new(case: :random) }
59
+
60
+ it "should contain at least one upper-case character" do
61
+ subject.emit_keyword(keyword).should =~ /[SELECT]/
62
+ end
63
+ end
64
+
65
+ context "when case is nil" do
66
+ subject { described_class.new(case: nil) }
67
+
68
+ let(:keyword) { 'Select' }
69
+
70
+ it "should emit the keyword as is" do
71
+ subject.emit_keyword(keyword).should == keyword
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "#emit_operator" do
77
+ context "when the operator is upper-case alphabetic text" do
78
+ subject { described_class.new(case: :lower) }
79
+
80
+ it "should emit a keyword" do
81
+ subject.emit_operator(:AS).should == 'as'
82
+ end
83
+ end
84
+
85
+ context "otherwise" do
86
+ it "should emit a String" do
87
+ subject.emit_operator(:"!=").should == '!='
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "#emit_null" do
93
+ it "should emit the NULL keyword" do
94
+ subject.emit_null.should == 'NULL'
95
+ end
96
+ end
97
+
98
+ describe "#emit_false" do
99
+ it "should emit 1=0" do
100
+ subject.emit_false.should == '1=0'
101
+ end
102
+ end
103
+
104
+ describe "#emit_true" do
105
+ it "should emit 1=1" do
106
+ subject.emit_true.should == '1=1'
107
+ end
108
+ end
109
+
110
+ describe "#emit_integer" do
111
+ it "should emit a String" do
112
+ subject.emit_integer(10).should == '10'
113
+ end
114
+ end
115
+
116
+ describe "#emit_decimal" do
117
+ it "should emit a String" do
118
+ subject.emit_decimal(2.5).should == '2.5'
119
+ end
120
+ end
121
+
122
+ describe "#emit_string" do
123
+ it "should emit a String" do
124
+ subject.emit_string("O'Brian").should == "'O''Brian'"
125
+ end
126
+
127
+ context "when :quotes is :double" do
128
+ subject { described_class.new(quotes: :double) }
129
+
130
+ it "should double quote Strings" do
131
+ subject.emit_string("O'Brian").should == "\"O'Brian\""
132
+ end
133
+ end
134
+ end
135
+
136
+ describe "#emit_field" do
137
+ it "should emit a String" do
138
+ subject.emit_field(:id).should == 'id'
139
+ end
140
+ end
141
+
142
+ describe "#emit_list" do
143
+ it "should emit a ',' separated list" do
144
+ subject.emit_list([1,2,3,'foo']).should == "(1,2,3,'foo')"
145
+ end
146
+ end
147
+
148
+ describe "#emit_assignments" do
149
+ let(:values) { {x: 1, y: 2} }
150
+
151
+ it "should emit a list of column names and values" do
152
+ subject.emit_assignments(values).should == 'x=1,y=2'
153
+ end
154
+ end
155
+
156
+ describe "#emit_expression" do
157
+ context "when the expression is a BinaryExpr" do
158
+ context "when the operator is alphabetic" do
159
+ subject { described_class.new(case: :upper) }
160
+
161
+ let(:expr) { SQL::BinaryExpr.new(:id,:is,1) }
162
+
163
+ it "should emit the operands and operator as a keyword with spaces" do
164
+ subject.emit_expression(expr).should == 'id IS 1'
165
+ end
166
+ end
167
+
168
+ context "when the operator is symbolic" do
169
+ let(:expr) { SQL::BinaryExpr.new(:id,:"=",1) }
170
+
171
+ it "should emit the operands and operator without spaces" do
172
+ subject.emit_expression(expr).should == 'id=1'
173
+ end
174
+ end
175
+
176
+ context "when the left-hand operand is a Statement" do
177
+ let(:expr) do
178
+ SQL::BinaryExpr.new(SQL::Statement.new(:SELECT,1),:"=",1)
179
+ end
180
+
181
+ it "should wrap the left-hand operand in parenthesis" do
182
+ subject.emit_expression(expr).should == '(SELECT 1)=1'
183
+ end
184
+ end
185
+
186
+ context "when the right-hand operand is a Statement" do
187
+ let(:expr) do
188
+ SQL::BinaryExpr.new(1,:"=",SQL::Statement.new(:SELECT,1))
189
+ end
190
+
191
+ it "should wrap the left-hand operand in parenthesis" do
192
+ subject.emit_expression(expr).should == '1=(SELECT 1)'
193
+ end
194
+ end
195
+ end
196
+
197
+ context "when the expression is a UnaryExpr" do
198
+ context "when the operator is upper-case alpha" do
199
+ let(:expr) { SQL::UnaryExpr.new(:NOT,:admin) }
200
+
201
+ it "should emit the operand and operator with spaces" do
202
+ subject.emit_expression(expr).should == 'NOT admin'
203
+ end
204
+ end
205
+
206
+ context "when the operator is symbolic" do
207
+ let(:expr) { SQL::UnaryExpr.new(:"-",1) }
208
+
209
+ it "should emit the operand and operator without spaces" do
210
+ subject.emit_expression(expr).should == '-1'
211
+ end
212
+ end
213
+
214
+ context "when the operand is a Statement" do
215
+ let(:expr) do
216
+ SQL::UnaryExpr.new(:NOT,SQL::Statement.new(:SELECT,1))
217
+ end
218
+
219
+ it "should wrap the operand in parenthesis" do
220
+ subject.emit_expression(expr).should == 'NOT (SELECT 1)'
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ describe "#emit_function" do
227
+ let(:func) { SQL::Function.new(:NOW) }
228
+
229
+ it "should emit the function name as a keyword" do
230
+ subject.emit_function(func).should == 'NOW()'
231
+ end
232
+
233
+ context "with arguments" do
234
+ let(:func) { SQL::Function.new(:MAX,1,2) }
235
+
236
+ it "should emit the function arguments" do
237
+ subject.emit_function(func).should == 'MAX(1,2)'
238
+ end
239
+ end
240
+ end
241
+
242
+ describe "#emit" do
243
+ context "when passed nil" do
244
+ it "should emit the NULL keyword" do
245
+ subject.emit(nil).should == 'NULL'
246
+ end
247
+ end
248
+
249
+ context "when passed true" do
250
+ it "should emit true" do
251
+ subject.emit(true).should == '1=1'
252
+ end
253
+ end
254
+
255
+ context "when passed false" do
256
+ it "should emit false" do
257
+ subject.emit(false).should == '1=0'
258
+ end
259
+ end
260
+
261
+ context "when passed an Integer" do
262
+ it "should emit an integer" do
263
+ subject.emit(10).should == '10'
264
+ end
265
+ end
266
+
267
+ context "when passed a Float" do
268
+ it "should emit a decimal" do
269
+ subject.emit(2.5).should == '2.5'
270
+ end
271
+ end
272
+
273
+ context "when passed a String" do
274
+ it "should emit a string" do
275
+ subject.emit("O'Brian").should == "'O''Brian'"
276
+ end
277
+ end
278
+
279
+ context "when passed a Literal" do
280
+ let(:literal) { SQL::Literal.new(42) }
281
+
282
+ it "should emit the value" do
283
+ subject.emit(literal).should == '42'
284
+ end
285
+ end
286
+
287
+ context "when passed a Field" do
288
+ let(:table) { SQL::Field.new(:users) }
289
+ let(:column) { SQL::Field.new(:id,table) }
290
+
291
+ it "should emit a field" do
292
+ subject.emit(column).should == 'users.id'
293
+ end
294
+ end
295
+
296
+ context "when passed a Symbol" do
297
+ it "should emit a field" do
298
+ subject.emit(:id).should == 'id'
299
+ end
300
+ end
301
+
302
+ context "when passed an Array" do
303
+ it "should emit a list" do
304
+ subject.emit([1,2,3,'foo']).should == "(1,2,3,'foo')"
305
+ end
306
+ end
307
+
308
+ context "when passed a Hash" do
309
+ it "should emit a list of assignments" do
310
+ subject.emit(x: 1, y: 2).should == 'x=1,y=2'
311
+ end
312
+ end
313
+
314
+ context "when passed a BinaryExpr" do
315
+ let(:expr) { SQL::BinaryExpr.new(:id,:"=",1) }
316
+
317
+ it "should emit an expression" do
318
+ subject.emit(expr).should == 'id=1'
319
+ end
320
+ end
321
+
322
+ context "when passed a UnaryExpr" do
323
+ let(:expr) { SQL::UnaryExpr.new(:NOT,:admin) }
324
+
325
+ it "should emit an expression" do
326
+ subject.emit(expr).should == 'NOT admin'
327
+ end
328
+ end
329
+
330
+ context "when passed a Statment" do
331
+ let(:stmt) { SQL::Statement.new(:SELECT,1) }
332
+
333
+ it "should emit a statement" do
334
+ subject.emit(stmt).should == 'SELECT 1'
335
+ end
336
+ end
337
+
338
+ context "when the object responds to #to_sql" do
339
+ let(:object) { double(:sql_object) }
340
+ let(:sql) { "EXEC sp_configure 'xp_cmdshell', 0;" }
341
+
342
+ it "should call #to_sql" do
343
+ object.stub(:to_sql).and_return(sql)
344
+
345
+ subject.emit(object).should == sql
346
+ end
347
+ end
348
+
349
+ context "otherwise" do
350
+ let(:object) { Object.new }
351
+
352
+ it "should raise an ArgumentError" do
353
+ lambda {
354
+ subject.emit(object)
355
+ }.should raise_error(ArgumentError)
356
+ end
357
+ end
358
+ end
359
+
360
+ describe "#emit_clause" do
361
+ let(:clause) { SQL::Clause.new(:"NOT INDEXED") }
362
+
363
+ it "should emit the clause keyword" do
364
+ subject.emit_clause(clause).should == "NOT INDEXED"
365
+ end
366
+
367
+ context "with an argument" do
368
+ let(:argument) { 100 }
369
+ let(:clause) { SQL::Clause.new(:LIMIT,argument) }
370
+
371
+ it "should also emit the clause argument" do
372
+ subject.emit_clause(clause).should == "LIMIT #{argument}"
373
+ end
374
+ end
375
+
376
+ context "with custom :space" do
377
+ subject { described_class.new(space: '/**/') }
378
+
379
+ let(:clause) { SQL::Clause.new(:LIMIT,100) }
380
+
381
+ it "should emit the custom white-space deliminater" do
382
+ subject.emit_clause(clause).should == 'LIMIT/**/100'
383
+ end
384
+ end
385
+ end
386
+
387
+ describe "#emit_clauses" do
388
+ let(:clauses) do
389
+ [
390
+ SQL::Clause.new(:LIMIT, 100),
391
+ SQL::Clause.new(:OFFSET, 10)
392
+ ]
393
+ end
394
+
395
+ it "should emit multiple clauses" do
396
+ subject.emit_clauses(clauses).should == 'LIMIT 100 OFFSET 10'
397
+ end
398
+
399
+ context "with custom :space" do
400
+ subject { described_class.new(space: '/**/') }
401
+
402
+ it "should emit the custom white-space deliminater" do
403
+ subject.emit_clauses(clauses).should == 'LIMIT/**/100/**/OFFSET/**/10'
404
+ end
405
+ end
406
+ end
407
+
408
+ describe "#emit_statement" do
409
+ subject { described_class.new(case: :lower) }
410
+
411
+ context "without an argument" do
412
+ let(:stmt) { SQL::Statement.new(:SELECT) }
413
+
414
+ it "should emit the statment keyword" do
415
+ subject.emit_statement(stmt).should == 'select'
416
+ end
417
+ end
418
+
419
+ context "with an argument" do
420
+ let(:stmt) { SQL::Statement.new(:SELECT,1) }
421
+
422
+ it "should emit the statment argument" do
423
+ subject.emit_statement(stmt).should == 'select 1'
424
+ end
425
+
426
+ context "with custom :space" do
427
+ subject { described_class.new(case: :lower, space: '/**/') }
428
+
429
+ it "should emit the custom white-space deliminater" do
430
+ subject.emit_statement(stmt).should == 'select/**/1'
431
+ end
432
+ end
433
+ end
434
+
435
+ context "with clauses" do
436
+ let(:stmt) { SQL::Statement.new(:SELECT,1).offset(1).limit(100) }
437
+
438
+ it "should emit the statment argument" do
439
+ subject.emit_statement(stmt).should == 'select 1 offset 1 limit 100'
440
+ end
441
+
442
+ context "with custom :space" do
443
+ subject { described_class.new(case: :lower, space: '/**/') }
444
+
445
+ it "should emit the custom white-space deliminater" do
446
+ subject.emit_statement(stmt).should == 'select/**/1/**/offset/**/1/**/limit/**/100'
447
+ end
448
+ end
449
+ end
450
+ end
451
+
452
+ describe "#emit_statement_list" do
453
+ let(:stmts) do
454
+ sql = SQL::StatementList.new
455
+ sql << SQL::Statement.new(:SELECT, 1)
456
+ sql << SQL::Statement.new([:DROP, :TABLE], :users)
457
+ sql
458
+ end
459
+
460
+ it "should emit multiple statements separated by '; '" do
461
+ subject.emit_statement_list(stmts).should == 'SELECT 1; DROP TABLE users'
462
+ end
463
+
464
+ context "with custom :space" do
465
+ subject { described_class.new(space: '/**/') }
466
+
467
+ it "should emit the custom white-space deliminater" do
468
+ subject.emit_statement_list(stmts).should == 'SELECT/**/1;/**/DROP/**/TABLE/**/users'
469
+ end
470
+ end
471
+ end
472
+ end