epugh-sequel 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,1105 @@
1
+ module Sequel
2
+ class Dataset
3
+ AND_SEPARATOR = " AND ".freeze
4
+ BOOL_FALSE = "'f'".freeze
5
+ BOOL_TRUE = "'t'".freeze
6
+ COLUMN_REF_RE1 = /\A([\w ]+)__([\w ]+)___([\w ]+)\z/.freeze
7
+ COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
8
+ COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
9
+ COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
10
+ IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
11
+ IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
12
+ N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
13
+ NULL = "NULL".freeze
14
+ QUESTION_MARK = '?'.freeze
15
+ STOCK_COUNT_OPTS = {:select => [LiteralString.new("COUNT(*)").freeze], :order => nil}.freeze
16
+ SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
17
+ TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
18
+ WILDCARD = '*'.freeze
19
+
20
+ # Adds an further filter to an existing filter using AND. If no filter
21
+ # exists an error is raised. This method is identical to #filter except
22
+ # it expects an existing filter.
23
+ def and(*cond, &block)
24
+ raise(Error::NoExistingFilter, "No existing filter found.") unless @opts[:having] || @opts[:where]
25
+ filter(*cond, &block)
26
+ end
27
+
28
+ # SQL fragment for the aliased expression
29
+ def aliased_expression_sql(ae)
30
+ as_sql(literal(ae.expression), ae.aliaz)
31
+ end
32
+
33
+ # SQL fragment for the SQL array.
34
+ def array_sql(a)
35
+ a.empty? ? '(NULL)' : "(#{expression_list(a)})"
36
+ end
37
+
38
+ # SQL fragment for specifying given CaseExpression.
39
+ def case_expression_sql(ce)
40
+ sql = '(CASE '
41
+ sql << "#{literal(ce.expression)} " if ce.expression
42
+ ce.conditions.collect{ |c,r|
43
+ sql << "WHEN #{literal(c)} THEN #{literal(r)} "
44
+ }
45
+ sql << "ELSE #{literal(ce.default)} END)"
46
+ end
47
+
48
+ # SQL fragment for the SQL CAST expression.
49
+ def cast_sql(expr, type)
50
+ "CAST(#{literal(expr)} AS #{db.send(:type_literal_base, :type=>type)})"
51
+ end
52
+
53
+ # SQL fragment for specifying all columns in a given table.
54
+ def column_all_sql(ca)
55
+ "#{quote_schema_table(ca.table)}.*"
56
+ end
57
+
58
+ # SQL fragment for complex expressions
59
+ def complex_expression_sql(op, args)
60
+ case op
61
+ when *IS_OPERATORS
62
+ v = IS_LITERALS[args.at(1)] || raise(Error, 'Invalid argument used for IS operator')
63
+ "(#{literal(args.at(0))} #{op} #{v})"
64
+ when *TWO_ARITY_OPERATORS
65
+ "(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
66
+ when *N_ARITY_OPERATORS
67
+ "(#{args.collect{|a| literal(a)}.join(" #{op} ")})"
68
+ when :NOT
69
+ "NOT #{literal(args.at(0))}"
70
+ when :NOOP
71
+ literal(args.at(0))
72
+ when :'B~'
73
+ "~#{literal(args.at(0))}"
74
+ else
75
+ raise(Sequel::Error, "invalid operator #{op}")
76
+ end
77
+ end
78
+
79
+ # Returns the number of records in the dataset.
80
+ def count
81
+ options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count : clone(STOCK_COUNT_OPTS).single_value.to_i
82
+ end
83
+
84
+ # Formats a DELETE statement using the given options and dataset options.
85
+ #
86
+ # dataset.filter{|o| o.price >= 100}.delete_sql #=>
87
+ # "DELETE FROM items WHERE (price >= 100)"
88
+ def delete_sql(opts = (defarg=true;nil))
89
+ Deprecation.deprecate("Calling Dataset#delete_sql with an argument is deprecated and will raise an error in a future version. Use dataset.clone(opts).delete_sql.") unless defarg
90
+ opts = opts ? @opts.merge(opts) : @opts
91
+
92
+ return static_sql(opts[:sql]) if opts[:sql]
93
+
94
+ if opts[:group]
95
+ raise Error::InvalidOperation, "Grouped datasets cannot be deleted from"
96
+ elsif opts[:from].is_a?(Array) && opts[:from].size > 1
97
+ raise Error::InvalidOperation, "Joined datasets cannot be deleted from"
98
+ end
99
+
100
+ sql = "DELETE FROM #{source_list(opts[:from])}"
101
+
102
+ if where = opts[:where]
103
+ sql << " WHERE #{literal(where)}"
104
+ end
105
+
106
+ sql
107
+ end
108
+
109
+ # Returns a copy of the dataset with the SQL DISTINCT clause.
110
+ # The DISTINCT clause is used to remove duplicate rows from the
111
+ # output. If arguments are provided, uses a DISTINCT ON clause,
112
+ # in which case it will only be distinct on those columns, instead
113
+ # of all returned columns.
114
+ def distinct(*args)
115
+ clone(:distinct => args)
116
+ end
117
+
118
+ # Adds an EXCEPT clause using a second dataset object. If all is true the
119
+ # clause used is EXCEPT ALL, which may return duplicate rows.
120
+ #
121
+ # DB[:items].except(DB[:other_items]).sql
122
+ # #=> "SELECT * FROM items EXCEPT SELECT * FROM other_items"
123
+ def except(dataset, all = false)
124
+ compound_clone(:except, dataset, all)
125
+ end
126
+
127
+ # Performs the inverse of Dataset#filter.
128
+ #
129
+ # dataset.exclude(:category => 'software').sql #=>
130
+ # "SELECT * FROM items WHERE (category != 'software')"
131
+ def exclude(*cond, &block)
132
+ clause = (@opts[:having] ? :having : :where)
133
+ cond = cond.first if cond.size == 1
134
+ cond = cond.sql_or if (Hash === cond) || ((Array === cond) && (cond.all_two_pairs?))
135
+ cond = filter_expr(cond, &block)
136
+ cond = SQL::BooleanExpression.invert(cond)
137
+ cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
138
+ clone(clause => cond)
139
+ end
140
+
141
+ # Returns an EXISTS clause for the dataset as a LiteralString.
142
+ #
143
+ # DB.select(1).where(DB[:items].exists).sql
144
+ # #=> "SELECT 1 WHERE EXISTS (SELECT * FROM items)"
145
+ def exists(opts = (defarg=true;nil))
146
+ Deprecation.deprecate("Calling Dataset#exists with an argument is deprecated and will raise an error in a future version. Use dataset.clone(opts).exists.") unless defarg
147
+ LiteralString.new("EXISTS (#{defarg ? select_sql : select_sql(opts)})")
148
+ end
149
+
150
+ # Returns a copy of the dataset with the given conditions imposed upon it.
151
+ # If the query already has a HAVING clause, then the conditions are imposed in the
152
+ # HAVING clause. If not, then they are imposed in the WHERE clause.
153
+ #
154
+ # filter accepts the following argument types:
155
+ #
156
+ # * Hash - list of equality/inclusion expressions
157
+ # * Array - depends:
158
+ # * If first member is a string, assumes the rest of the arguments
159
+ # are parameters and interpolates them into the string.
160
+ # * If all members are arrays of length two, treats the same way
161
+ # as a hash, except it allows for duplicate keys to be
162
+ # specified.
163
+ # * String - taken literally
164
+ # * Symbol - taken as a boolean column argument (e.g. WHERE active)
165
+ # * Sequel::SQL::BooleanExpression - an existing condition expression,
166
+ # probably created using the Sequel expression filter DSL.
167
+ #
168
+ # filter also takes a block, which should return one of the above argument
169
+ # types, and is treated the same way. If both a block and regular argument
170
+ # are provided, they get ANDed together.
171
+ #
172
+ # Examples:
173
+ #
174
+ # dataset.filter(:id => 3).sql #=>
175
+ # "SELECT * FROM items WHERE (id = 3)"
176
+ # dataset.filter('price < ?', 100).sql #=>
177
+ # "SELECT * FROM items WHERE price < 100"
178
+ # dataset.filter([[:id, (1,2,3)], [:id, 0..10]]).sql #=>
179
+ # "SELECT * FROM items WHERE ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))"
180
+ # dataset.filter('price < 100').sql #=>
181
+ # "SELECT * FROM items WHERE price < 100"
182
+ # dataset.filter(:active).sql #=>
183
+ # "SELECT * FROM items WHERE :active
184
+ # dataset.filter{|o| o.price < 100}.sql #=>
185
+ # "SELECT * FROM items WHERE (price < 100)"
186
+ #
187
+ # Multiple filter calls can be chained for scoping:
188
+ #
189
+ # software = dataset.filter(:category => 'software')
190
+ # software.filter{|o| o.price < 100}.sql #=>
191
+ # "SELECT * FROM items WHERE ((category = 'software') AND (price < 100))"
192
+ #
193
+ # See doc/dataset_filters.rdoc for more examples and details.
194
+ def filter(*cond, &block)
195
+ _filter(@opts[:having] ? :having : :where, *cond, &block)
196
+ end
197
+
198
+ # The first source (primary table) for this dataset. If the dataset doesn't
199
+ # have a table, raises an error. If the table is aliased, returns the aliased name.
200
+ def first_source
201
+ source = @opts[:from]
202
+ if source.nil? || source.empty?
203
+ raise Error, 'No source specified for query'
204
+ end
205
+ case s = source.first
206
+ when Hash
207
+ s.values.first
208
+ when Symbol
209
+ sch, table, aliaz = split_symbol(s)
210
+ aliaz ? aliaz.to_sym : s
211
+ else
212
+ s
213
+ end
214
+ end
215
+
216
+ # Returns a copy of the dataset with the source changed.
217
+ def from(*source)
218
+ clone(:from => source)
219
+ end
220
+
221
+ # Returns a dataset selecting from the current dataset.
222
+ #
223
+ # ds = DB[:items].order(:name)
224
+ # ds.sql #=> "SELECT * FROM items ORDER BY name"
225
+ # ds.from_self.sql #=> "SELECT * FROM (SELECT * FROM items ORDER BY name)"
226
+ def from_self
227
+ fs = {}
228
+ @opts.keys.each{|k| fs[k] = nil}
229
+ fs[:from] = [self]
230
+ clone(fs)
231
+ end
232
+
233
+ # SQL fragment specifying an SQL function call
234
+ def function_sql(f)
235
+ args = f.args
236
+ "#{f.f}#{args.empty? ? '()' : literal(args)}"
237
+ end
238
+
239
+ # Pattern match any of the columns to any of the terms. The terms can be
240
+ # strings (which use LIKE) or regular expressions (which are only supported
241
+ # in some databases). See Sequel::SQL::StringExpression.like. Note that the
242
+ # total number of pattern matches will be cols.length * terms.length,
243
+ # which could cause performance issues.
244
+ def grep(cols, terms)
245
+ filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
246
+ end
247
+
248
+ # Returns a copy of the dataset with the results grouped by the value of
249
+ # the given columns
250
+ def group(*columns)
251
+ clone(:group => columns)
252
+ end
253
+ alias_method :group_by, :group
254
+
255
+ # Returns a copy of the dataset with the HAVING conditions changed. Raises
256
+ # an error if the dataset has not been grouped. See #filter for argument types.
257
+ def having(*cond, &block)
258
+ raise(Error::InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
259
+ _filter(:having, *cond, &block)
260
+ end
261
+
262
+ # Inserts multiple values. If a block is given it is invoked for each
263
+ # item in the given array before inserting it. See #multi_insert as
264
+ # a possible faster version that inserts multiple records in one
265
+ # SQL statement.
266
+ def insert_multiple(array, &block)
267
+ if block
268
+ array.each {|i| insert(block[i])}
269
+ else
270
+ array.each {|i| insert(i)}
271
+ end
272
+ end
273
+
274
+ # Formats an INSERT statement using the given values. If a hash is given,
275
+ # the resulting statement includes column names. If no values are given,
276
+ # the resulting statement includes a DEFAULT VALUES clause.
277
+ #
278
+ # dataset.insert_sql() #=> 'INSERT INTO items DEFAULT VALUES'
279
+ # dataset.insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
280
+ # dataset.insert_sql(:a => 1, :b => 2) #=>
281
+ # 'INSERT INTO items (a, b) VALUES (1, 2)'
282
+ def insert_sql(*values)
283
+ return static_sql(@opts[:sql]) if @opts[:sql]
284
+
285
+ from = source_list(@opts[:from])
286
+ case values.size
287
+ when 0
288
+ values = {}
289
+ when 1
290
+ vals = values.at(0)
291
+ if [Hash, Dataset, Array].any?{|c| vals.is_a?(c)}
292
+ values = vals
293
+ elsif vals.respond_to?(:values)
294
+ values = vals.values
295
+ end
296
+ end
297
+
298
+ case values
299
+ when Array
300
+ if values.empty?
301
+ insert_default_values_sql
302
+ else
303
+ "INSERT INTO #{from} VALUES #{literal(values)}"
304
+ end
305
+ when Hash
306
+ values = @opts[:defaults].merge(values) if @opts[:defaults]
307
+ values = values.merge(@opts[:overrides]) if @opts[:overrides]
308
+ values = transform_save(values) if @transform
309
+ if values.empty?
310
+ insert_default_values_sql
311
+ else
312
+ fl, vl = [], []
313
+ values.each do |k, v|
314
+ fl << literal(String === k ? k.to_sym : k)
315
+ vl << literal(v)
316
+ end
317
+ "INSERT INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
318
+ end
319
+ when Dataset
320
+ "INSERT INTO #{from} #{literal(values)}"
321
+ end
322
+ end
323
+
324
+ # Adds an INTERSECT clause using a second dataset object. If all is true
325
+ # the clause used is INTERSECT ALL, which may return duplicate rows.
326
+ #
327
+ # DB[:items].intersect(DB[:other_items]).sql
328
+ # #=> "SELECT * FROM items INTERSECT SELECT * FROM other_items"
329
+ def intersect(dataset, all = false)
330
+ compound_clone(:intersect, dataset, all)
331
+ end
332
+
333
+ # Inverts the current filter
334
+ #
335
+ # dataset.filter(:category => 'software').invert.sql #=>
336
+ # "SELECT * FROM items WHERE (category != 'software')"
337
+ def invert
338
+ having, where = @opts[:having], @opts[:where]
339
+ raise(Error, "No current filter") unless having || where
340
+ o = {}
341
+ o[:having] = SQL::BooleanExpression.invert(having) if having
342
+ o[:where] = SQL::BooleanExpression.invert(where) if where
343
+ clone(o)
344
+ end
345
+
346
+ # SQL fragment specifying an Irregular (cast/extract) SQL function call
347
+ def irregular_function_sql(f)
348
+ "#{f.f}(#{literal(f.arg1)} #{f.joiner} #{literal(f.arg2)})"
349
+ end
350
+
351
+ # SQL fragment specifying a JOIN clause without ON or USING.
352
+ def join_clause_sql(jc)
353
+ table = jc.table
354
+ table_alias = jc.table_alias
355
+ table_alias = nil if table == table_alias
356
+ tref = table_ref(table)
357
+ " #{join_type_sql(jc.join_type)} #{table_alias ? as_sql(tref, table_alias) : tref}"
358
+ end
359
+
360
+ # SQL fragment specifying a JOIN clause with ON.
361
+ def join_on_clause_sql(jc)
362
+ "#{join_clause_sql(jc)} ON #{literal(filter_expr(jc.on))}"
363
+ end
364
+
365
+ # SQL fragment specifying a JOIN clause with USING.
366
+ def join_using_clause_sql(jc)
367
+ "#{join_clause_sql(jc)} USING (#{column_list(jc.using)})"
368
+ end
369
+
370
+ # Returns a joined dataset. Uses the following arguments:
371
+ #
372
+ # * type - The type of join to do (:inner, :left_outer, :right_outer, :full)
373
+ # * table - Depends on type:
374
+ # * Dataset - a subselect is performed with an alias of tN for some value of N
375
+ # * Model (or anything responding to :table_name) - table.table_name
376
+ # * String, Symbol: table
377
+ # * expr - specifies conditions, depends on type:
378
+ # * Hash, Array with all two pairs - Assumes key (1st arg) is column of joined table (unless already
379
+ # qualified), and value (2nd arg) is column of the last joined or primary table (or the
380
+ # :implicit_qualifier option).
381
+ # To specify multiple conditions on a single joined table column, you must use an array.
382
+ # Uses a JOIN with an ON clause.
383
+ # * Array - If all members of the array are symbols, considers them as columns and
384
+ # uses a JOIN with a USING clause. Most databases will remove duplicate columns from
385
+ # the result set if this is used.
386
+ # * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
387
+ # or CROSS join. If a block is given, uses a ON clause based on the block, see below.
388
+ # * Everything else - pretty much the same as a using the argument in a call to filter,
389
+ # so strings are considered literal, symbols specify boolean columns, and blockless
390
+ # filter expressions can be used. Uses a JOIN with an ON clause.
391
+ # * options - a hash of options, with any of the following keys:
392
+ # * :table_alias - the name of the table's alias when joining, necessary for joining
393
+ # to the same table more than once. No alias is used by default.
394
+ # * :implicit_qualifer - The name to use for qualifying implicit conditions. By default,
395
+ # the last joined or primary table is used.
396
+ # * block - The block argument should only be given if a JOIN with an ON clause is used,
397
+ # in which case it yields the table alias/name for the table currently being joined,
398
+ # the table alias/name for the last joined (or first table), and an array of previous
399
+ # SQL::JoinClause.
400
+ def join_table(type, table, expr=nil, options={}, &block)
401
+ if [Symbol, String].any?{|c| options.is_a?(c)}
402
+ table_alias = options
403
+ last_alias = nil
404
+ else
405
+ table_alias = options[:table_alias]
406
+ last_alias = options[:implicit_qualifier]
407
+ end
408
+ if Dataset === table
409
+ if table_alias.nil?
410
+ table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
411
+ table_alias = "t#{table_alias_num}"
412
+ end
413
+ table_name = table_alias
414
+ else
415
+ table = table.table_name if table.respond_to?(:table_name)
416
+ table_name = table_alias || table
417
+ end
418
+
419
+ join = if expr.nil? and !block_given?
420
+ SQL::JoinClause.new(type, table, table_alias)
421
+ elsif Array === expr and !expr.empty? and expr.all?{|x| Symbol === x}
422
+ raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
423
+ SQL::JoinUsingClause.new(expr, type, table, table_alias)
424
+ else
425
+ last_alias ||= @opts[:last_joined_table] || (first_source.is_a?(Dataset) ? 't1' : first_source)
426
+ if Hash === expr or (Array === expr and expr.all_two_pairs?)
427
+ expr = expr.collect do |k, v|
428
+ k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
429
+ v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
430
+ [k,v]
431
+ end
432
+ end
433
+ if block_given?
434
+ expr2 = yield(table_name, last_alias, @opts[:join] || [])
435
+ expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
436
+ end
437
+ SQL::JoinOnClause.new(expr, type, table, table_alias)
438
+ end
439
+
440
+ opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
441
+ opts[:num_dataset_sources] = table_alias_num if table_alias_num
442
+ clone(opts)
443
+ end
444
+
445
+ # If given an integer, the dataset will contain only the first l results.
446
+ # If given a range, it will contain only those at offsets within that
447
+ # range. If a second argument is given, it is used as an offset.
448
+ def limit(l, o = nil)
449
+ return from_self.limit(l, o) if @opts[:sql]
450
+
451
+ if Range === l
452
+ o = l.first
453
+ l = l.last - l.first + (l.exclude_end? ? 0 : 1)
454
+ end
455
+ l = l.to_i
456
+ raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
457
+ opts = {:limit => l}
458
+ if o
459
+ o = o.to_i
460
+ raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
461
+ opts[:offset] = o
462
+ end
463
+ clone(opts)
464
+ end
465
+
466
+ # Returns a literal representation of a value to be used as part
467
+ # of an SQL expression.
468
+ #
469
+ # dataset.literal("abc'def\\") #=> "'abc''def\\\\'"
470
+ # dataset.literal(:items__id) #=> "items.id"
471
+ # dataset.literal([1, 2, 3]) => "(1, 2, 3)"
472
+ # dataset.literal(DB[:items]) => "(SELECT * FROM items)"
473
+ # dataset.literal(:x + 1 > :y) => "((x + 1) > y)"
474
+ #
475
+ # If an unsupported object is given, an exception is raised.
476
+ def literal(v)
477
+ case v
478
+ when String
479
+ return v if v.is_a?(LiteralString)
480
+ v.is_a?(SQL::Blob) ? literal_blob(v) : literal_string(v)
481
+ when Symbol
482
+ literal_symbol(v)
483
+ when Integer
484
+ literal_integer(v)
485
+ when Hash
486
+ literal_hash(v)
487
+ when SQL::Expression
488
+ literal_expression(v)
489
+ when Float
490
+ literal_float(v)
491
+ when BigDecimal
492
+ literal_big_decimal(v)
493
+ when NilClass
494
+ NULL
495
+ when TrueClass
496
+ literal_true
497
+ when FalseClass
498
+ literal_false
499
+ when Array
500
+ literal_array(v)
501
+ when Time
502
+ literal_time(v)
503
+ when DateTime
504
+ literal_datetime(v)
505
+ when Date
506
+ literal_date(v)
507
+ when Dataset
508
+ literal_dataset(v)
509
+ else
510
+ literal_other(v)
511
+ end
512
+ end
513
+
514
+ # Returns an array of insert statements for inserting multiple records.
515
+ # This method is used by #multi_insert to format insert statements and
516
+ # expects a keys array and and an array of value arrays.
517
+ #
518
+ # This method should be overridden by descendants if the support
519
+ # inserting multiple records in a single SQL statement.
520
+ def multi_insert_sql(columns, values)
521
+ s = "INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES "
522
+ values.map{|r| s + literal(r)}
523
+ end
524
+
525
+ # Adds an alternate filter to an existing filter using OR. If no filter
526
+ # exists an error is raised.
527
+ def or(*cond, &block)
528
+ clause = (@opts[:having] ? :having : :where)
529
+ cond = cond.first if cond.size == 1
530
+ if @opts[clause]
531
+ clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
532
+ else
533
+ raise Error::NoExistingFilter, "No existing filter found."
534
+ end
535
+ end
536
+
537
+ # Returns a copy of the dataset with the order changed. If a nil is given
538
+ # the returned dataset has no order. This can accept multiple arguments
539
+ # of varying kinds, and even SQL functions.
540
+ #
541
+ # ds.order(:name).sql #=> 'SELECT * FROM items ORDER BY name'
542
+ # ds.order(:a, :b).sql #=> 'SELECT * FROM items ORDER BY a, b'
543
+ # ds.order('a + b'.lit).sql #=> 'SELECT * FROM items ORDER BY a + b'
544
+ # ds.order(:a + :b).sql #=> 'SELECT * FROM items ORDER BY (a + b)'
545
+ # ds.order(:name.desc).sql #=> 'SELECT * FROM items ORDER BY name DESC'
546
+ # ds.order(:name.asc).sql #=> 'SELECT * FROM items ORDER BY name ASC'
547
+ # ds.order(:arr|1).sql #=> 'SELECT * FROM items ORDER BY arr[1]'
548
+ # ds.order(nil).sql #=> 'SELECT * FROM items'
549
+ def order(*columns, &block)
550
+ columns += Array(virtual_row_block_call(block)) if block
551
+ clone(:order => (columns.compact.empty?) ? nil : columns)
552
+ end
553
+ alias_method :order_by, :order
554
+
555
+ # Returns a copy of the dataset with the order columns added
556
+ # to the existing order.
557
+ def order_more(*columns, &block)
558
+ order(*Array(@opts[:order]).concat(columns), &block)
559
+ end
560
+
561
+ # SQL fragment for the ordered expression, used in the ORDER BY
562
+ # clause.
563
+ def ordered_expression_sql(oe)
564
+ "#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
565
+ end
566
+
567
+ # SQL fragment for a literal string with placeholders
568
+ def placeholder_literal_string_sql(pls)
569
+ args = pls.args.dup
570
+ s = pls.str.gsub(QUESTION_MARK){literal(args.shift)}
571
+ s = "(#{s})" if pls.parens
572
+ s
573
+ end
574
+
575
+ # SQL fragment for the qualifed identifier, specifying
576
+ # a table and a column (or schema and table).
577
+ def qualified_identifier_sql(qcr)
578
+ [qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
579
+ end
580
+
581
+ # Adds quoting to identifiers (columns and tables). If identifiers are not
582
+ # being quoted, returns name as a string. If identifiers are being quoted
583
+ # quote the name with quoted_identifier.
584
+ def quote_identifier(name)
585
+ return name if name.is_a?(LiteralString)
586
+ name = input_identifier(name)
587
+ name = quoted_identifier(name) if quote_identifiers?
588
+ name
589
+ end
590
+
591
+ # Separates the schema from the table and returns a string with them
592
+ # quoted (if quoting identifiers)
593
+ def quote_schema_table(table)
594
+ schema, table = schema_and_table(table)
595
+ "#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
596
+ end
597
+
598
+ # This method quotes the given name with the SQL standard double quote.
599
+ # should be overridden by subclasses to provide quoting not matching the
600
+ # SQL standard, such as backtick (used by MySQL and SQLite).
601
+ def quoted_identifier(name)
602
+ "\"#{name.to_s.gsub('"', '""')}\""
603
+ end
604
+
605
+ # Returns a copy of the dataset with the order reversed. If no order is
606
+ # given, the existing order is inverted.
607
+ def reverse_order(*order)
608
+ order(*invert_order(order.empty? ? @opts[:order] : order))
609
+ end
610
+ alias_method :reverse, :reverse_order
611
+
612
+ # Split the schema information from the table
613
+ def schema_and_table(table_name)
614
+ sch = db.default_schema if db
615
+ case table_name
616
+ when Symbol
617
+ s, t, a = split_symbol(table_name)
618
+ [s||sch, t]
619
+ when SQL::QualifiedIdentifier
620
+ [table_name.table, table_name.column]
621
+ when SQL::Identifier
622
+ [sch, table_name.value]
623
+ when String
624
+ [sch, table_name]
625
+ else
626
+ raise Error, 'table_name should be a Symbol, SQL::QualifiedIdentifier, SQL::Identifier, or String'
627
+ end
628
+ end
629
+
630
+ # Returns a copy of the dataset with the columns selected changed
631
+ # to the given columns.
632
+ def select(*columns, &block)
633
+ columns += Array(virtual_row_block_call(block)) if block
634
+ clone(:select => columns)
635
+ end
636
+
637
+ # Returns a copy of the dataset selecting the wildcard.
638
+ def select_all
639
+ clone(:select => nil)
640
+ end
641
+
642
+ # Returns a copy of the dataset with the given columns added
643
+ # to the existing selected columns.
644
+ def select_more(*columns, &block)
645
+ select(*Array(@opts[:select]).concat(columns), &block)
646
+ end
647
+
648
+ # Formats a SELECT statement using the given options and the dataset
649
+ # options.
650
+ def select_sql(opts = (defarg=true;nil))
651
+ Deprecation.deprecate("Calling Dataset#select_sql with an argument is deprecated and will raise an error in a future version. Use dataset.clone(opts).select_sql.") unless defarg
652
+ opts = opts ? @opts.merge(opts) : @opts
653
+ return static_sql(opts[:sql]) if opts[:sql]
654
+ sql = 'SELECT'
655
+ select_clause_order.each{|x| send("select_#{x}_sql", sql, opts)}
656
+ sql
657
+ end
658
+
659
+ # Same as select_sql, not aliased directly to make subclassing simpler.
660
+ def sql(opts = (defarg=true;nil))
661
+ Deprecation.deprecate("Calling Dataset#select_sql with an argument is deprecated and will raise an error in a future version. Use dataset.clone(opts).select_sql.") unless defarg
662
+ defarg ? select_sql : select_sql(opts)
663
+ end
664
+
665
+ # SQL fragment for specifying subscripts (SQL arrays)
666
+ def subscript_sql(s)
667
+ "#{s.f}[#{s.sub.join(COMMA_SEPARATOR)}]"
668
+ end
669
+
670
+ # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
671
+ def unfiltered
672
+ clone(:where => nil, :having => nil)
673
+ end
674
+
675
+ # Adds a UNION clause using a second dataset object. If all is true the
676
+ # clause used is UNION ALL, which may return duplicate rows.
677
+ #
678
+ # DB[:items].union(DB[:other_items]).sql
679
+ # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
680
+ def union(dataset, all = false)
681
+ compound_clone(:union, dataset, all)
682
+ end
683
+
684
+ # Returns a copy of the dataset with no order.
685
+ def unordered
686
+ order(nil)
687
+ end
688
+
689
+ # Formats an UPDATE statement using the given values.
690
+ #
691
+ # dataset.update_sql(:price => 100, :category => 'software') #=>
692
+ # "UPDATE items SET price = 100, category = 'software'"
693
+ #
694
+ # Accepts a block, but such usage is discouraged.
695
+ #
696
+ # Raises an error if the dataset is grouped or includes more
697
+ # than one table.
698
+ def update_sql(values = {}, opts = (defarg=true;nil))
699
+ Deprecation.deprecate("Calling Dataset#update_sql with an argument is deprecated and will raise an error in a future version. Use dataset.clone(opts).update_sql.") unless defarg
700
+ opts = opts ? @opts.merge(opts) : @opts
701
+
702
+ return static_sql(opts[:sql]) if opts[:sql]
703
+
704
+ if opts[:group]
705
+ raise Error::InvalidOperation, "A grouped dataset cannot be updated"
706
+ elsif (opts[:from].size > 1) or opts[:join]
707
+ raise Error::InvalidOperation, "A joined dataset cannot be updated"
708
+ end
709
+
710
+ sql = "UPDATE #{source_list(@opts[:from])} SET "
711
+ set = if values.is_a?(Hash)
712
+ values = opts[:defaults].merge(values) if opts[:defaults]
713
+ values = values.merge(opts[:overrides]) if opts[:overrides]
714
+ # get values from hash
715
+ values = transform_save(values) if @transform
716
+ values.map do |k, v|
717
+ "#{[String, Symbol].any?{|c| k.is_a?(c)} ? quote_identifier(k) : literal(k)} = #{literal(v)}"
718
+ end.join(COMMA_SEPARATOR)
719
+ else
720
+ # copy values verbatim
721
+ values
722
+ end
723
+ sql << set
724
+ if where = opts[:where]
725
+ sql << " WHERE #{literal(where)}"
726
+ end
727
+
728
+ sql
729
+ end
730
+
731
+ # Add a condition to the WHERE clause. See #filter for argument types.
732
+ def where(*cond, &block)
733
+ _filter(:where, *cond, &block)
734
+ end
735
+
736
+ # Returns a copy of the dataset with the static SQL used. This is useful if you want
737
+ # to keep the same row_proc/transform/graph, but change the SQL used to custom SQL.
738
+ def with_sql(sql, *args)
739
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
740
+ clone(:sql=>sql)
741
+ end
742
+
743
+ [:inner, :full_outer, :right_outer, :left_outer].each do |jtype|
744
+ class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end")
745
+ end
746
+ alias_method :join, :inner_join
747
+
748
+ protected
749
+
750
+ # Returns a table reference for use in the FROM clause. Returns an SQL subquery
751
+ # frgament with an optional table alias.
752
+ def to_table_reference(table_alias=nil)
753
+ s = "(#{sql})"
754
+ table_alias ? as_sql(s, table_alias) : s
755
+ end
756
+
757
+ private
758
+
759
+ # Internal filter method so it works on either the having or where clauses.
760
+ def _filter(clause, *cond, &block)
761
+ cond = cond.first if cond.size == 1
762
+ cond = transform_save(cond) if @transform if cond.is_a?(Hash)
763
+ cond = filter_expr(cond, &block)
764
+ cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
765
+ clone(clause => cond)
766
+ end
767
+
768
+ # SQL fragment for specifying an alias. expression should already be literalized.
769
+ def as_sql(expression, aliaz)
770
+ "#{expression} AS #{quote_identifier(aliaz)}"
771
+ end
772
+
773
+ # Converts an array of column names into a comma seperated string of
774
+ # column names. If the array is empty, a wildcard (*) is returned.
775
+ def column_list(columns)
776
+ if columns.nil? || columns.empty?
777
+ WILDCARD
778
+ else
779
+ m = columns.map do |i|
780
+ i.is_a?(Hash) ? i.map{|k, v| as_sql(literal(k), v)} : literal(i)
781
+ end
782
+ m.join(COMMA_SEPARATOR)
783
+ end
784
+ end
785
+
786
+ # Add the dataset to the list of compounds
787
+ def compound_clone(type, dataset, all)
788
+ clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset, all]])
789
+ end
790
+
791
+ # Converts an array of expressions into a comma separated string of
792
+ # expressions.
793
+ def expression_list(columns)
794
+ columns.map{|i| literal(i)}.join(COMMA_SEPARATOR)
795
+ end
796
+
797
+ # SQL fragment based on the expr type. See #filter.
798
+ def filter_expr(expr = nil, &block)
799
+ expr = nil if expr == []
800
+ if expr && block
801
+ return SQL::BooleanExpression.new(:AND, filter_expr(expr), filter_expr(block))
802
+ elsif block
803
+ expr = block
804
+ end
805
+ case expr
806
+ when Hash
807
+ SQL::BooleanExpression.from_value_pairs(expr)
808
+ when Array
809
+ if String === expr[0]
810
+ SQL::PlaceholderLiteralString.new(expr.shift, expr, true)
811
+ else
812
+ SQL::BooleanExpression.from_value_pairs(expr)
813
+ end
814
+ when Proc
815
+ filter_expr(virtual_row_block_call(expr))
816
+ when SQL::NumericExpression, SQL::StringExpression
817
+ raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
818
+ when Symbol, SQL::Expression
819
+ expr
820
+ when TrueClass, FalseClass
821
+ SQL::BooleanExpression.new(:NOOP, expr)
822
+ when String
823
+ LiteralString.new("(#{expr})")
824
+ else
825
+ raise(Error, 'Invalid filter argument')
826
+ end
827
+ end
828
+
829
+ # SQL fragment specifying a list of identifiers
830
+ def identifier_list(columns)
831
+ columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
832
+ end
833
+
834
+ # SQL statement for formatting an insert statement with default values
835
+ def insert_default_values_sql
836
+ "INSERT INTO #{source_list(@opts[:from])} DEFAULT VALUES"
837
+ end
838
+
839
+ # Inverts the given order by breaking it into a list of column references
840
+ # and inverting them.
841
+ #
842
+ # dataset.invert_order([:id.desc]]) #=> [:id]
843
+ # dataset.invert_order(:category, :price.desc]) #=>
844
+ # [:category.desc, :price]
845
+ def invert_order(order)
846
+ return nil unless order
847
+ new_order = []
848
+ order.map do |f|
849
+ case f
850
+ when SQL::OrderedExpression
851
+ SQL::OrderedExpression.new(f.expression, !f.descending)
852
+ else
853
+ SQL::OrderedExpression.new(f)
854
+ end
855
+ end
856
+ end
857
+
858
+ # SQL fragment specifying a JOIN type, converts underscores to
859
+ # spaces and upcases.
860
+ def join_type_sql(join_type)
861
+ "#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
862
+ end
863
+
864
+ # SQL fragment for Array. Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
865
+ def literal_array(v)
866
+ v.all_two_pairs? ? literal_expression(v.sql_expr) : array_sql(v)
867
+ end
868
+
869
+ # SQL fragment for BigDecimal
870
+ def literal_big_decimal(v)
871
+ d = v.to_s("F")
872
+ v.nan? || v.infinite? ? "'#{d}'" : d
873
+ end
874
+
875
+ # SQL fragment for SQL::Blob
876
+ def literal_blob(v)
877
+ literal_string(v)
878
+ end
879
+
880
+ # SQL fragment for Dataset. Does a subselect inside parantheses.
881
+ def literal_dataset(v)
882
+ "(#{subselect_sql(v)})"
883
+ end
884
+
885
+ # SQL fragment for Date, using the ISO8601 format.
886
+ def literal_date(v)
887
+ "'#{v}'"
888
+ end
889
+
890
+ # SQL fragment for DateTime, using the ISO8601 format.
891
+ def literal_datetime(v)
892
+ "'#{v}'"
893
+ end
894
+
895
+ # SQL fragment for SQL::Expression, result depends on the specific type of expression.
896
+ def literal_expression(v)
897
+ v.to_s(self)
898
+ end
899
+
900
+ # SQL fragment for false
901
+ def literal_false
902
+ BOOL_FALSE
903
+ end
904
+
905
+ # SQL fragment for Float
906
+ def literal_float(v)
907
+ v.to_s
908
+ end
909
+
910
+ # SQL fragment for Hash, treated as an expression
911
+ def literal_hash(v)
912
+ literal_expression(v.sql_expr)
913
+ end
914
+
915
+ # SQL fragment for Integer
916
+ def literal_integer(v)
917
+ v.to_s
918
+ end
919
+
920
+ # SQL fragmento for a type of object not handled by Dataset#literal. Raises an error. If a database specific type is allowed, this should be overriden in a subclass.
921
+ def literal_other(v)
922
+ raise Error, "can't express #{v.inspect} as a SQL literal"
923
+ end
924
+
925
+ # SQL fragment for String. Doubles \ and ' by default.
926
+ def literal_string(v)
927
+ "'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
928
+ end
929
+
930
+ # Converts a symbol into a column name. This method supports underscore
931
+ # notation in order to express qualified (two underscores) and aliased
932
+ # (three underscores) columns:
933
+ #
934
+ # ds = DB[:items]
935
+ # :abc.to_column_ref(ds) #=> "abc"
936
+ # :abc___a.to_column_ref(ds) #=> "abc AS a"
937
+ # :items__abc.to_column_ref(ds) #=> "items.abc"
938
+ # :items__abc___a.to_column_ref(ds) #=> "items.abc AS a"
939
+ def literal_symbol(v)
940
+ c_table, column, c_alias = split_symbol(v)
941
+ qc = "#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}"
942
+ c_alias ? as_sql(qc, c_alias) : qc
943
+ end
944
+
945
+ # SQL fragment for Time, uses the ISO8601 format.
946
+ def literal_time(v)
947
+ "'#{v.iso8601}'"
948
+ end
949
+
950
+ # SQL fragment for true.
951
+ def literal_true
952
+ BOOL_TRUE
953
+ end
954
+
955
+ # Returns a qualified column name (including a table name) if the column
956
+ # name isn't already qualified.
957
+ def qualified_column_name(column, table)
958
+ if Symbol === column
959
+ c_table, column, c_alias = split_symbol(column)
960
+ unless c_table
961
+ case table
962
+ when Symbol
963
+ schema, table, t_alias = split_symbol(table)
964
+ t_alias ||= Sequel::SQL::QualifiedIdentifier.new(schema, table) if schema
965
+ when Sequel::SQL::AliasedExpression
966
+ t_alias = table.aliaz
967
+ end
968
+ c_table = t_alias || table
969
+ end
970
+ ::Sequel::SQL::QualifiedIdentifier.new(c_table, column)
971
+ else
972
+ column
973
+ end
974
+ end
975
+
976
+ # The order of methods to call to build the SELECT SQL statement
977
+ def select_clause_order
978
+ SELECT_CLAUSE_ORDER
979
+ end
980
+
981
+ # Modify the sql to add the columns selected
982
+ def select_columns_sql(sql, opts)
983
+ sql << " #{column_list(opts[:select])}"
984
+ end
985
+
986
+ # Modify the sql to add the DISTINCT modifier
987
+ def select_distinct_sql(sql, opts)
988
+ if distinct = opts[:distinct]
989
+ sql << " DISTINCT#{" ON (#{expression_list(distinct)})" unless distinct.empty?}"
990
+ end
991
+ end
992
+
993
+ # Modify the sql to add a dataset to the via an EXCEPT, INTERSECT, or UNION clause.
994
+ # This uses a subselect for the compound datasets used, because using parantheses doesn't
995
+ # work on all databases. I consider this an ugly hack, but can't I think of a better default.
996
+ def select_compounds_sql(sql, opts)
997
+ return unless opts[:compounds]
998
+ opts[:compounds].each do |type, dataset, all|
999
+ compound_sql = subselect_sql(dataset)
1000
+ compound_sql = "SELECT * FROM (#{compound_sql})" if dataset.opts[:compounds]
1001
+ sql.replace("#{sql} #{type.to_s.upcase}#{' ALL' if all} #{compound_sql}")
1002
+ end
1003
+ end
1004
+
1005
+ # Modify the sql to add the list of tables to select FROM
1006
+ def select_from_sql(sql, opts)
1007
+ sql << " FROM #{source_list(opts[:from])}" if opts[:from]
1008
+ end
1009
+
1010
+ # Modify the sql to add the expressions to GROUP BY
1011
+ def select_group_sql(sql, opts)
1012
+ sql << " GROUP BY #{expression_list(opts[:group])}" if opts[:group]
1013
+ end
1014
+
1015
+ # Modify the sql to add the filter criteria in the HAVING clause
1016
+ def select_having_sql(sql, opts)
1017
+ sql << " HAVING #{literal(opts[:having])}" if opts[:having]
1018
+ end
1019
+
1020
+ # Modify the sql to add the list of tables to JOIN to
1021
+ def select_join_sql(sql, opts)
1022
+ opts[:join].each{|j| sql << literal(j)} if opts[:join]
1023
+ end
1024
+
1025
+ # Modify the sql to limit the number of rows returned and offset
1026
+ def select_limit_sql(sql, opts)
1027
+ sql << " LIMIT #{opts[:limit]}" if opts[:limit]
1028
+ sql << " OFFSET #{opts[:offset]}" if opts[:offset]
1029
+ end
1030
+
1031
+ # Modify the sql to add the expressions to ORDER BY
1032
+ def select_order_sql(sql, opts)
1033
+ sql << " ORDER BY #{expression_list(opts[:order])}" if opts[:order]
1034
+ end
1035
+
1036
+ # Modify the sql to add the filter criteria in the WHERE clause
1037
+ def select_where_sql(sql, opts)
1038
+ sql << " WHERE #{literal(opts[:where])}" if opts[:where]
1039
+ end
1040
+
1041
+ # Converts an array of source names into into a comma separated list.
1042
+ def source_list(source)
1043
+ if source.nil? || source.empty?
1044
+ raise Error, 'No source specified for query'
1045
+ end
1046
+ auto_alias_count = @opts[:num_dataset_sources] || 0
1047
+ m = source.map do |s|
1048
+ case s
1049
+ when Dataset
1050
+ auto_alias_count += 1
1051
+ s.to_table_reference("t#{auto_alias_count}")
1052
+ else
1053
+ table_ref(s)
1054
+ end
1055
+ end
1056
+ m.join(COMMA_SEPARATOR)
1057
+ end
1058
+
1059
+ # Splits the symbol into three parts. Each part will
1060
+ # either be a string or nil.
1061
+ #
1062
+ # For columns, these parts are the table, column, and alias.
1063
+ # For tables, these parts are the schema, table, and alias.
1064
+ def split_symbol(sym)
1065
+ s = sym.to_s
1066
+ if m = COLUMN_REF_RE1.match(s)
1067
+ m[1..3]
1068
+ elsif m = COLUMN_REF_RE2.match(s)
1069
+ [nil, m[1], m[2]]
1070
+ elsif m = COLUMN_REF_RE3.match(s)
1071
+ [m[1], m[2], nil]
1072
+ else
1073
+ [nil, s, nil]
1074
+ end
1075
+ end
1076
+
1077
+ # SQL to use if this dataset uses static SQL. Since static SQL
1078
+ # can be a PlaceholderLiteralString in addition to a String,
1079
+ # we literalize nonstrings.
1080
+ def static_sql(sql)
1081
+ sql.is_a?(String) ? sql : literal(sql)
1082
+ end
1083
+
1084
+ # SQL fragment for a subselect using the given database's SQL.
1085
+ def subselect_sql(ds)
1086
+ ds.sql
1087
+ end
1088
+
1089
+ # SQL fragment specifying a table name.
1090
+ def table_ref(t)
1091
+ case t
1092
+ when Symbol
1093
+ literal_symbol(t)
1094
+ when Dataset
1095
+ t.to_table_reference
1096
+ when Hash
1097
+ t.map{|k, v| as_sql(table_ref(k), v)}.join(COMMA_SEPARATOR)
1098
+ when String
1099
+ quote_identifier(t)
1100
+ else
1101
+ literal(t)
1102
+ end
1103
+ end
1104
+ end
1105
+ end