colincasey-sequel 2.10.0 → 2.10.1

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