sql-maker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/CHANGELOG.md +3 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +23 -0
  7. data/Rakefile +14 -0
  8. data/lib/sql-maker.rb +5 -0
  9. data/lib/sql/maker.rb +676 -0
  10. data/lib/sql/maker/condition.rb +378 -0
  11. data/lib/sql/maker/error.rb +3 -0
  12. data/lib/sql/maker/helper.rb +22 -0
  13. data/lib/sql/maker/quoting.rb +138 -0
  14. data/lib/sql/maker/select.rb +544 -0
  15. data/lib/sql/maker/select/oracle.rb +30 -0
  16. data/lib/sql/maker/select_set.rb +194 -0
  17. data/lib/sql/maker/util.rb +54 -0
  18. data/lib/sql/query_maker.rb +429 -0
  19. data/scripts/perl2ruby.rb +34 -0
  20. data/spec/maker/bind_param_spec.rb +62 -0
  21. data/spec/maker/condition/add_raw_spec.rb +29 -0
  22. data/spec/maker/condition/compose_empty_spec.rb +72 -0
  23. data/spec/maker/condition/empty_values_spec.rb +25 -0
  24. data/spec/maker/condition/make_term_spec.rb +38 -0
  25. data/spec/maker/condition/where_spec.rb +35 -0
  26. data/spec/maker/delete_spec.rb +116 -0
  27. data/spec/maker/insert_empty_spec.rb +23 -0
  28. data/spec/maker/insert_spec.rb +61 -0
  29. data/spec/maker/new_line_spec.rb +9 -0
  30. data/spec/maker/select/oracle/oracle_spec.rb +16 -0
  31. data/spec/maker/select/pod_select_spec.rb +34 -0
  32. data/spec/maker/select/statement_spec.rb +805 -0
  33. data/spec/maker/select_set_spec.rb +294 -0
  34. data/spec/maker/select_spec.rb +470 -0
  35. data/spec/maker/simple_spec.rb +54 -0
  36. data/spec/maker/strict_spec.rb +45 -0
  37. data/spec/maker/subquery_spec.rb +77 -0
  38. data/spec/maker/update_spec.rb +138 -0
  39. data/spec/maker/util_spec.rb +6 -0
  40. data/spec/maker/where_spec.rb +28 -0
  41. data/spec/query_maker/and_using_hash_spec.rb +13 -0
  42. data/spec/query_maker/cheatsheet_spec.rb +32 -0
  43. data/spec/query_maker/column_bind_spec.rb +55 -0
  44. data/spec/query_maker/refs_in_bind_spec.rb +19 -0
  45. data/spec/spec_helper.rb +15 -0
  46. data/sql-maker.gemspec +25 -0
  47. metadata +185 -0
@@ -0,0 +1,30 @@
1
+ require 'sql/maker/select'
2
+
3
+ class SQL::Maker::Select::Oracle < SQL::Maker::Select
4
+ ## Oracle doesn't have the LIMIT clause.
5
+ def as_limit
6
+ return ''
7
+ end
8
+
9
+ ## Override as_sql to emulate the LIMIT clause.
10
+ def as_sql
11
+ limit = self.limit
12
+ offset = self.offset
13
+
14
+ if limit && offset
15
+ self.add_select( "ROW_NUMBER() OVER (ORDER BY 1) R" )
16
+ end
17
+
18
+ sql = super
19
+
20
+ if limit
21
+ sql = "SELECT * FROM ( #{sql} ) WHERE "
22
+ if offset
23
+ sql = sql + " R BETWEEN #{offset} + 1 AND #{limit} + #{offset}"
24
+ else
25
+ sql = sql + " rownum <= #{limit}"
26
+ end
27
+ end
28
+ return sql
29
+ end
30
+ end
@@ -0,0 +1,194 @@
1
+ require 'sql/maker/select'
2
+
3
+ class SQL::Maker::SelectSet
4
+ include ::SQL::Maker::Util
5
+
6
+ FNOP = %w[union union_all intersect intersect_all except except_all]
7
+ FNOP.each do |fn|
8
+ method = "sql_#{fn}" # sql_union
9
+ operator = fn.upcase.gsub(/_/, ' ')
10
+
11
+ define_singleton_method(method) do |*statements|
12
+ stmt = SQL::Maker::SelectSet.new(
13
+ :operator => operator,
14
+ :new_line => statements.first.new_line,
15
+ )
16
+ statements.each {|statement| stmt.add_statement(statement) }
17
+ stmt
18
+ end
19
+ end
20
+
21
+ attr_accessor :new_line, :operator, :statements, :quote_char, :name_sep, :order_by
22
+
23
+ def initialize(args = {})
24
+ croak("Missing mandatory parameter 'operator' for SQL::Maker::SelectSet.new") unless args[:operator]
25
+ @new_line = args[:new_line] || "\n"
26
+ @operator = args[:operator]
27
+ @quote_char = args[:quote_char]
28
+ @name_sep = args[:name_sep]
29
+ @statements = []
30
+ @order_by = []
31
+ end
32
+
33
+ def add_statement(statement)
34
+ unless statement.respond_to?(:as_sql)
35
+ croak( "'statement' doesn't have 'as_sql' method.")
36
+ end
37
+ self.statements.push statement
38
+ self # method chain
39
+ end
40
+
41
+ def as_sql_order_by
42
+ attrs = self.order_by
43
+ return '' if attrs.empty?
44
+
45
+ return 'ORDER BY ' + attrs.map {|e|
46
+ col, type = e
47
+ type ? self._quote(col) + " #{type}" : self._quote(col)
48
+ }.join(', ')
49
+ end
50
+
51
+ def _quote(label)
52
+ SQL::Maker::Util.quote_identifier(label, self.quote_char, self.name_sep)
53
+ end
54
+
55
+ def as_sql
56
+ new_line = self.new_line
57
+ operator = self.operator
58
+
59
+ sql = self.statements.map {|st| st.as_sql }.join(new_line + operator + new_line)
60
+ sql += ' ' + self.as_sql_order_by unless self.order_by.empty?
61
+ sql
62
+ end
63
+ alias_method :to_s, :as_sql
64
+
65
+ def bind
66
+ bind = []
67
+ self.statements.each do |select|
68
+ bind += select.bind
69
+ end
70
+ bind
71
+ end
72
+
73
+ def add_order_by(*args)
74
+ col, type = parse_args(*args)
75
+ self.order_by += [[col, type]]
76
+ self # method chain
77
+ end
78
+ end
79
+
80
+ __END__
81
+
82
+ =head1 NAME
83
+
84
+ SQL::Maker::SelectSet - provides set functions
85
+
86
+ =head1 SYNOPSIS
87
+
88
+ use SQL::Maker::SelectSet qw(union_all except)
89
+ s1 = SQL::Maker::Select .new()
90
+ .add_select('foo')
91
+ .add_from('t1')
92
+ s2 = SQL::Maker::Select .new()
93
+ .add_select('bar')
94
+ .add_from('t2')
95
+ union_all( s1, s2 ).as_sql
96
+ # =>
97
+ # SQL::Maker::SelectSet.new_set(
98
+ # :operator => 'UNION ALL',
99
+ # :new_line => s1.new_line
100
+ # ).add_statement(s1)
101
+ # .add_statement(s2)
102
+ # .as_sql
103
+ # => "SELECT foo FROM t1 UNION ALL SELECT bar FROM t2"
104
+ except( s1, s2 ).as_sql
105
+ # => SQL::Maker::SelectSet.new_set( :operator => 'EXCEPT', :new_line => s1.new_line )
106
+ # .add_statement( s1 )
107
+ # .add_statement( s2 )
108
+ # .as_sql
109
+ # => "SELECT foo FROM t1 EXCEPT SELECT bar FROM t2"
110
+
111
+ =head1 DESCRIPTION
112
+
113
+ This module provides some set functions which return a SQL::Maker::SelectSet object
114
+ inherited from L<SQL::Maker::Select>.
115
+
116
+ =head1 FUNCTION
117
+
118
+ =over 4
119
+
120
+ =item C<< union(select :SQL::Maker::Select | set :SQL::Maker::SelectSet) : SQL::Maker::SelectSet >>
121
+
122
+ Tow statements are combined by C<UNION>.
123
+
124
+ =item C<< union_all(select :SQL::Maker::Select | set :SQL::Maker::SelectSet) : SQL::Maker::SelectSet >>
125
+
126
+ Tow statements are combined by C<UNION ALL>.
127
+
128
+ =item C<< intersect(select :SQL::Maker::Select | set :SQL::Maker::SelectSet) : SQL::Maker::SelectSet >>
129
+
130
+ Tow statements are combined by C<INTERSECT>.
131
+
132
+ =item C<< intersect_all(select :SQL::Maker::Select | set :SQL::Maker::SelectSet) : SQL::Maker::SelectSet >>
133
+
134
+ Tow statements are combined by C<INTERSECT ALL>.
135
+
136
+ =item C<< except(select :SQL::Maker::Select | set :SQL::Maker::SelectSet) : SQL::Maker::SelectSet >>
137
+
138
+ Tow statements are combined by C<EXCEPT>.
139
+
140
+ =item C<< except(select :SQL::Maker::Select | set :SQL::Maker::SelectSet) : SQL::Maker::SelectSet >>
141
+
142
+ Tow statements are combined by C<EXCEPT ALL>.
143
+
144
+ =back
145
+
146
+ =head1 Class Method
147
+
148
+ =over 4
149
+
150
+ =item stmt = SQL::Maker::SelectSet.new( %args )
151
+
152
+ opretaor is a set operator (ex. C<UNION>).
153
+ one and another are SQL::Maker::Select object or SQL::Maker::SelectSet object.
154
+ It returns a SQL::Maker::SelectSet object.
155
+
156
+ The parameters are:
157
+
158
+ =over 4
159
+
160
+ =item new_line
161
+
162
+ Default values is "\n".
163
+
164
+ =item operator : Str
165
+
166
+ The operator. This parameter is required.
167
+
168
+ =back
169
+
170
+ =back
171
+
172
+ =head1 Instance Methods
173
+
174
+ =over 4
175
+
176
+ =item C<< sql = set.as_sql() : Str >>
177
+
178
+ Returns a new select statement.
179
+
180
+ =item C<< @bind = set.bind() : Array[Str] >>
181
+
182
+ Returns bind variables.
183
+
184
+ =item C<< set.add_statement(stmt : stmt.can('as_sql')) : SQL::Maker::SelectSet >>
185
+
186
+ This method adds new statement object. C<< stmt >> must provides 'as_sql' method.
187
+
188
+ I<Return Value> is the set itself.
189
+
190
+ =back
191
+
192
+ =head1 SEE ALSO
193
+
194
+ L<SQL::Maker::Select>
@@ -0,0 +1,54 @@
1
+ require 'sql/maker/error'
2
+ require 'sql/maker/quoting'
3
+
4
+ module SQL::Maker::Util
5
+ def self.included(klass)
6
+ klass.extend(self)
7
+ end
8
+
9
+ def croak(message)
10
+ raise SQL::Maker::Error.new(message)
11
+ end
12
+
13
+ def array_wrap(val)
14
+ val.is_a?(Array) ? val : [val]
15
+ end
16
+
17
+ # perl-like argument parser of my ($a, $b) = @_;
18
+ def parse_args(*args)
19
+ if args.size > 1
20
+ # method('a', 'b') #=> ['a', 'b']
21
+ return args
22
+ else
23
+ args = args.first
24
+ case args
25
+ when Hash
26
+ # method('a' => 'b') #=> ['a', 'b']
27
+ # method('a' => ['b', 'c']) #=> ['a', ['b', 'c']]
28
+ # method('a' => {'b' => 'c'}) #=> ['a', {'b' => 'c'}]
29
+ args.each.first
30
+ when Array
31
+ # method(['a', 'b']) #=> ['a', 'b']
32
+ return args
33
+ else
34
+ # method('a') #=> ['a', nil]
35
+ return [args, nil]
36
+ end
37
+ end
38
+ end
39
+
40
+ def quote_identifier(label, quote_char, name_sep)
41
+ return label if label == '*';
42
+ return label unless name_sep;
43
+ label.to_s.split(/#{Regexp.escape(name_sep)}/).map {|e| e == '*' ? e : "#{quote_char}#{e}#{quote_char}" }.join(name_sep)
44
+ end
45
+ module_function :quote_identifier
46
+ public :quote_identifier
47
+
48
+ def bind_param(sql, bind)
49
+ raise SQL::Maker::Error.new('bind arity mismatch') if sql.count('?') != bind.size
50
+ i = -1; sql.gsub('?') { SQL::Maker::Quoting.quote(bind[i+=1]) }
51
+ end
52
+ module_function :bind_param
53
+ public :bind_param
54
+ end
@@ -0,0 +1,429 @@
1
+ require 'sql/maker/util'
2
+
3
+ class SQL::QueryMaker
4
+ include SQL::Maker::Util
5
+
6
+ FNOP = {
7
+ 'is_null' => 'IS NULL',
8
+ 'is_not_null' => 'IS NOT NULL',
9
+ 'eq' => '= ?',
10
+ 'ne' => '!= ?',
11
+ 'lt' => '< ?',
12
+ 'gt' => '> ?',
13
+ 'le' => '<= ?',
14
+ 'ge' => '>= ?',
15
+ 'like' => 'LIKE ?',
16
+ 'between' => 'BETWEEN ? AND ?',
17
+ 'not_between' => 'NOT BETWEEN ? AND ?',
18
+ 'not' => 'NOT @',
19
+ }
20
+
21
+ class << self
22
+ %w[and or].each do |_|
23
+ fn = "sql_#{_}"
24
+ op = _.upcase
25
+
26
+ define_method(fn) do |args|
27
+ column = nil
28
+ if args.is_a?(Hash)
29
+ if args.each.first[1].is_a?(Array)
30
+ # :foo => [v1, v2, v3]
31
+ # :foo => [sql_ge(min), sql_lt(max)]
32
+ column, args = args.each.first
33
+ else
34
+ # {:foo => 1, :bar => sql_eq(2), baz => sql_lt(3)}
35
+ conds = []
36
+ args.each do |column, value|
37
+ if value.respond_to?(:bind_column)
38
+ value.bind_column(column)
39
+ else
40
+ value = sql_eq(column, value)
41
+ end
42
+ conds.push(value)
43
+ end
44
+ args = conds
45
+ end
46
+ elsif args.is_a?(Array)
47
+ # [sql_eq(:foo => v1), sql_eq(:bar => v2)]
48
+ # [:foo => v1, :bar => sql_lt(v2)]
49
+ else
50
+ croak("arguments to `#{op}` must be an array or a hash")
51
+ end
52
+ # build and return the compiler
53
+ return SQL::QueryMaker.new(column, Proc.new {|column, quote_cb|
54
+ next op == 'AND' ? '0=1' : '1=1' if args.empty?
55
+ terms = []
56
+ args.each do |arg|
57
+ if arg.respond_to?(:as_sql)
58
+ (t, bind) = arg.as_sql(column, quote_cb)
59
+ terms.push "(#{t})"
60
+ else
61
+ croak("no column binding for fn") unless column
62
+ terms.push '(' + quote_cb.call(column) + ' = ?)'
63
+ end
64
+ end
65
+ term = terms.join " #{op} "
66
+ }, Proc.new {
67
+ bind = []
68
+ args.each do |arg|
69
+ if arg.respond_to?(:bind)
70
+ bind += arg.bind
71
+ else
72
+ bind += [arg]
73
+ end
74
+ end
75
+ bind
76
+ }.call)
77
+ end
78
+ end
79
+
80
+ %w[in not_in].each do |_|
81
+ fn = "sql_#{_}"
82
+ op = _.upcase.gsub(/_/, ' ')
83
+
84
+ define_method(fn) do |args|
85
+ column = nil
86
+ if args.is_a?(Hash)
87
+ if args.each.first[1].is_a?(Array)
88
+ # :foo => [v1, v2, v3]
89
+ column, args = args.each.first
90
+ else
91
+ croak("arguments to `#{op}` must be an {key => array}")
92
+ end
93
+ elsif args.is_a?(Array)
94
+ # [v1, v2, v3] # bind column later
95
+ else
96
+ croak("arguments to `#{op}` must be an array or a hash")
97
+ end
98
+ return SQL::QueryMaker.new(column, Proc.new {|column, quote_cb|
99
+ croak("no column binding for #{fn}") unless column
100
+ next op == 'IN' ? '0=1' : '1=1' if args.empty?
101
+ terms = []
102
+ args.each do |arg|
103
+ if arg.respond_to?(:as_sql)
104
+ t = arg.as_sql(nil, quote_cb)
105
+ terms.push(t == '?' ? t : "(#{t})") # emit parens only when necessary
106
+ else
107
+ terms.push '?'
108
+ end
109
+ end
110
+ term = quote_cb.call(column) + " #{op} (" + terms.join(',') + ')'
111
+ }, Proc.new {
112
+ bind = []
113
+ args.each do |arg|
114
+ if arg.respond_to?(:bind)
115
+ bind += arg.bind
116
+ else
117
+ bind += [arg]
118
+ end
119
+ end
120
+ bind
121
+ }.call)
122
+ end
123
+ end
124
+
125
+ FNOP.each do |_, expr|
126
+ fn = "sql_#{_}"
127
+
128
+ define_method(fn) do |*args|
129
+ (num_args, builder) = _compile_builder(expr)
130
+ column = nil
131
+ if args.first.is_a?(Hash)
132
+ # sql_eq(foo: => 3)
133
+ column, args = args.first.each.first
134
+ args = array_wrap(args)
135
+ else
136
+ if args.size > num_args
137
+ # sql_is_null('foo')
138
+ column, args = [args.first, args[1..-1]]
139
+ else
140
+ column, args = [nil, args]
141
+ end
142
+ end
143
+ croak("the operator expects num_args parameters, but got #{args.size}") if num_args != args.size
144
+ return _sql_op(fn, builder, column, args)
145
+ end
146
+ end
147
+
148
+ # sql_op('IN (SELECT foo_id FROM bar WHERE t=?)', [44])
149
+ # sql_op('foo','IN (SELECT foo_id FROM bar WHERE t=?)', [44])
150
+ def sql_op(*args)
151
+ column, expr, bind = (args.size >= 3 ? args : [nil] + args)
152
+ (num_bind, builder) = _compile_builder(expr)
153
+ croak("the operator expects num_bind but got #{bind.size}") if num_bind != bind.size
154
+ return _sql_op("sql_op", builder, column, bind)
155
+ end
156
+
157
+ def _sql_op(fn, builder, column, bind)
158
+ return SQL::QueryMaker.new(column, Proc.new {|column, quote_cb|
159
+ croak("no column binding for fn(bind...)") unless column
160
+ term = builder.call(quote_cb.call(column))
161
+ }, bind)
162
+ end
163
+
164
+ def sql_raw(*args)
165
+ sql, bind = parse_args(*args)
166
+ return SQL::QueryMaker.new(nil, Proc.new { sql }, bind)
167
+ end
168
+
169
+ def _compile_builder(expr)
170
+ # substitute the column character
171
+ expr = "@ #{expr}" if expr !~ /@/
172
+ num_args = expr.count('?')
173
+ exprs = expr.split(/@/, -1)
174
+ builder = Proc.new {|quoted_column|
175
+ exprs.join(quoted_column)
176
+ }
177
+ return [num_args, builder]
178
+ end
179
+ end
180
+
181
+ attr_accessor :column, :as_sql, :bind
182
+ def initialize(column, as_sql, bind)
183
+ bind = bind.nil? ? [] : array_wrap(bind)
184
+ bind.each do |b|
185
+ croak("cannot bind an array or an hash") if b.is_a?(Array) or b.is_a?(Hash)
186
+ end
187
+ @column = column
188
+ @as_sql = as_sql
189
+ @bind = bind
190
+ end
191
+
192
+ def bind_column(column = nil)
193
+ if column
194
+ croak('cannot rebind column for \`' + self.column + "` to: `column`") if self.column
195
+ end
196
+ @column = column
197
+ end
198
+
199
+ def as_sql(supplied_colname = nil, quote_cb = nil)
200
+ self.bind_column(supplied_colname) if supplied_colname
201
+ quote_cb ||= self.method(:quote_identifier)
202
+ return @as_sql.call(@column, quote_cb)
203
+ end
204
+
205
+ def quote_identifier(label)
206
+ label.to_s.split(/\./).map {|e| "`#{e}`"}.join('.')
207
+ end
208
+ end
209
+
210
+ __END__
211
+
212
+ =head1 NAME
213
+
214
+ SQL::QueryMaker - helper functions for SQL query generation
215
+
216
+ =head1 SYNOPSIS
217
+
218
+ query = sql_eq(:foo => v)
219
+ query.as_sql # `foo`=?
220
+ query.bind # (v)
221
+
222
+ query = sql_lt(:foo => v)
223
+ query.as_sql # `foo`<?
224
+ query.bind # (v)
225
+
226
+ query = sql_in(:foo => [
227
+ v1, v2, v3,
228
+ ])
229
+ query.as_sql # `foo` IN (?,?,?)
230
+ query.bind # (v1,v2,v3)
231
+
232
+ query = sql_and(:foo => [
233
+ sql_ge(min),
234
+ sql_lt(max)
235
+ ])
236
+ query.as_sql # `foo`>=? AND `foo`<?
237
+ query.bind # (min,max)
238
+
239
+ query = sql_and([
240
+ sql_eq(:foo => v1),
241
+ sql_eq(:bar => v2)
242
+ ]
243
+ query.as_sql # `foo`=? AND `bar`=?
244
+ query.bind # (v1,v2)
245
+
246
+ query = sql_and([
247
+ :foo => v1,
248
+ :bar => sql_lt(v2),
249
+ ])
250
+ query.as_sql # `foo`=? AND `bar`<?
251
+ query.bind # (v1,v2)
252
+
253
+ =head1 DESCRIPTION
254
+
255
+ This module concentrates on providing an expressive, concise way to declare SQL
256
+ expressions by exporting carefully-designed functions.
257
+ It is possible to use the module to generate SQL query conditions and pass them
258
+ as arguments to other more versatile query builders such as L<SQL::Maker>.
259
+
260
+ The functions exported by the module instantiate comparator objects that build
261
+ SQL expressions when their C<as_sql> method are being invoked.
262
+ There are two ways to specify the names of the columns to the comparator; to
263
+ pass in the names as argument or to specify then as an argument to the
264
+ C<as_sql> method.
265
+
266
+ =head1 FUNCTIONS
267
+
268
+ =head2 C<< sql_eq([column,] value) >>
269
+
270
+ =head2 C<< sql_lt([column,] value) >>
271
+
272
+ =head2 C<< sql_gt([column,] value) >>
273
+
274
+ =head2 C<< sql_le([column,] value) >>
275
+
276
+ =head2 C<< sql_ge([column,] value) >>
277
+
278
+ =head2 C<< sql_like([column,] value) >>
279
+
280
+ =head2 C<< sql_is_null([column]) >>
281
+
282
+ =head2 C<< sql_is_not_null([column]) >>
283
+
284
+ =head2 C<< sql_not([column]) >>
285
+
286
+ =head2 C<< sql_between([column,] min_value, max_value) >>
287
+
288
+ =head2 C<< sql_not_between([column,] min_value, max_value) >>
289
+
290
+ =head2 C<< sql_in([column,] \@values) >>
291
+
292
+ =head2 C<< sql_not_in([column,] \@values) >>
293
+
294
+ Instantiates a comparator object that tests a column against given value(s).
295
+
296
+ =head2 C<< sql_and([column,] \@conditions) >>
297
+
298
+ =head2 C<< sql_or([$ column,] \@conditions) >>
299
+
300
+ Aggregates given comparator objects into a logical expression.
301
+
302
+ If specified, the column name is pushed down to the arguments when the
303
+ C<as_sql> method is being called, as show in the second example below.
304
+
305
+ sql_and([ # => `foo`=? AND `bar`<?
306
+ sql_eq("foo" => v1),
307
+ sql_lt("bar" => v2)
308
+ ])
309
+
310
+ sql_and("foo" => [ # => `foo`>=min OR `foo`<max
311
+ sql_ge(min),
312
+ sql_lt(max),
313
+ ])
314
+
315
+ =head2 C<< sql_and(\%conditions) >>
316
+
317
+ =head2 C<< sql_or(\%conditions) >>
318
+
319
+ Aggregates given pairs of column names and comparators into a logical
320
+ expression.
321
+
322
+ The value part is composed of as the argument to the C<=> operator if it is
323
+ not a blessed reference.
324
+
325
+ query = sql_and({
326
+ :foo => 'abc',
327
+ :bar => sql_lt(123),
328
+ })
329
+ query.as_sql # => `foo`=? AND bar<?
330
+ query.bind # => ('abc', 123)
331
+
332
+
333
+ =head2 C<< sql_op([column,] op_sql, \@bind_values) >>
334
+
335
+ Generates a comparator object that tests a column using the given SQL and
336
+ values. C<<@>> in the given SQL are replaced by the column name (specified
337
+ either by the argument to the function or later by the call to the C<<as_sql>>
338
+ method), and C<<?>> are substituted by the given bind values.
339
+
340
+ =head2 C<< sql_raw(sql, @bind_values) >>
341
+
342
+ Generates a comparator object from raw SQL and bind values. C<<?>> in the
343
+ given SQL are replaced by the bind values.
344
+
345
+ =head2 C<< obj.as_sql() >>
346
+
347
+ =head2 C<< obj.as_sql(column_name) >>
348
+
349
+ =head2 C<< obj.as_sql(column_name, quote_identifier_cb) >>
350
+
351
+ Compiles given comparator object and returns an SQL expression.
352
+ Corresponding bind values should be obtained by calling the C<bind> method.
353
+
354
+ The function optionally accepts a column name to which the comparator object
355
+ should be bound; an error is thrown if the comparator object is already bound
356
+ to another column.
357
+
358
+ The function also accepts a callback for quoting the identifiers. If omitted,
359
+ the identifiers are quoted using C<`> after being splitted using C<.>; i.e. a
360
+ column designated as C<foo.bar> is quoted as C<`foo`.`bar`>.
361
+
362
+ =head2 C<< obj.bind() >>
363
+
364
+ Returns a list of bind values corresponding to the SQL expression returned by
365
+ the C<as_sql> method.
366
+
367
+ =head1 CHEAT SHEET
368
+
369
+ IN: sql_eq('foo' => 'bar')
370
+ OUT QUERY: '`foo` = ?'
371
+ OUT BIND: ['bar']
372
+
373
+ IN: sql_in('foo' => ['bar', 'baz'])
374
+ OUT QUERY: '`foo` IN (?,?)'
375
+ OUT BIND: ['bar','baz']
376
+
377
+ IN: sql_and([sql_eq('foo' => 'bar'), sql_eq('baz' => 123)])
378
+ OUT QUERY: '(`foo` = ?) AND (`baz` = ?)'
379
+ OUT BIND: ['bar',123]
380
+
381
+ IN: sql_and('foo' => [sql_ge(3), sql_lt(5)])
382
+ OUT QUERY: '(`foo` >= ?) AND (`foo` < ?)'
383
+ OUT BIND: [3,5]
384
+
385
+ IN: sql_or([sql_eq('foo' => 'bar'), sql_eq('baz' => 123)])
386
+ OUT QUERY: '(`foo` = ?) OR (`baz` = ?)'
387
+ OUT BIND: ['bar',123]
388
+
389
+ IN: sql_or('foo' => ['bar', 'baz'])
390
+ OUT QUERY: '(`foo` = ?) OR (`foo` = ?)'
391
+ OUT BIND: ['bar','baz']
392
+
393
+ IN: sql_is_null('foo')
394
+ OUT QUERY: '`foo` IS NULL'
395
+ OUT BIND: []
396
+
397
+ IN: sql_is_not_null('foo')
398
+ OUT QUERY: '`foo` IS NOT NULL'
399
+ OUT BIND: []
400
+
401
+ IN: sql_between('foo', 1, 2)
402
+ OUT QUERY: '`foo` BETWEEN ? AND ?'
403
+ OUT BIND: [1,2]
404
+
405
+ IN: sql_not('foo')
406
+ OUT QUERY: 'NOT `foo`'
407
+ OUT BIND: []
408
+
409
+ IN: sql_op('apples', 'MATCH (@) AGAINST (?)', ['oranges'])
410
+ OUT QUERY: 'MATCH (`apples`) AGAINST (?)'
411
+ OUT BIND: ['oranges']
412
+
413
+ IN: sql_raw('SELECT * FROM t WHERE id=?',123)
414
+ OUT QUERY: 'SELECT * FROM t WHERE id=?'
415
+ OUT BIND: [123]
416
+
417
+ IN: sql_in('foo' => [123,sql_raw('SELECT id FROM t WHERE cat=?',5)])
418
+ OUT QUERY: '`foo` IN (?,(SELECT id FROM t WHERE cat=?))'
419
+ OUT BIND: [123,5]
420
+
421
+ =head1 AUTHOR
422
+
423
+ Natoshi Seo (Originally designed by Kazuho Oku as a Perl module)
424
+
425
+ =head1 LICENSE
426
+
427
+ This library is free software; you can redistribute it and/or modify it under the MIT License.
428
+
429
+ =cut