ronin-sql 0.2.4 → 1.0.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.
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