sequel 3.10.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG +68 -0
  2. data/COPYING +1 -1
  3. data/README.rdoc +87 -27
  4. data/bin/sequel +2 -4
  5. data/doc/association_basics.rdoc +1383 -0
  6. data/doc/dataset_basics.rdoc +106 -0
  7. data/doc/opening_databases.rdoc +45 -16
  8. data/doc/querying.rdoc +210 -0
  9. data/doc/release_notes/3.11.0.txt +254 -0
  10. data/doc/virtual_rows.rdoc +217 -31
  11. data/lib/sequel/adapters/ado.rb +28 -12
  12. data/lib/sequel/adapters/ado/mssql.rb +33 -1
  13. data/lib/sequel/adapters/amalgalite.rb +13 -8
  14. data/lib/sequel/adapters/db2.rb +1 -2
  15. data/lib/sequel/adapters/dbi.rb +7 -4
  16. data/lib/sequel/adapters/do.rb +14 -15
  17. data/lib/sequel/adapters/do/postgres.rb +4 -5
  18. data/lib/sequel/adapters/do/sqlite.rb +9 -0
  19. data/lib/sequel/adapters/firebird.rb +5 -10
  20. data/lib/sequel/adapters/informix.rb +2 -4
  21. data/lib/sequel/adapters/jdbc.rb +111 -49
  22. data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
  24. data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
  27. data/lib/sequel/adapters/mysql.rb +14 -5
  28. data/lib/sequel/adapters/odbc.rb +2 -4
  29. data/lib/sequel/adapters/odbc/mssql.rb +2 -4
  30. data/lib/sequel/adapters/openbase.rb +1 -2
  31. data/lib/sequel/adapters/oracle.rb +4 -8
  32. data/lib/sequel/adapters/postgres.rb +4 -11
  33. data/lib/sequel/adapters/shared/mssql.rb +22 -9
  34. data/lib/sequel/adapters/shared/mysql.rb +33 -30
  35. data/lib/sequel/adapters/shared/oracle.rb +0 -5
  36. data/lib/sequel/adapters/shared/postgres.rb +13 -11
  37. data/lib/sequel/adapters/shared/sqlite.rb +56 -10
  38. data/lib/sequel/adapters/sqlite.rb +16 -9
  39. data/lib/sequel/connection_pool.rb +6 -1
  40. data/lib/sequel/connection_pool/single.rb +1 -0
  41. data/lib/sequel/core.rb +6 -1
  42. data/lib/sequel/database.rb +52 -23
  43. data/lib/sequel/database/schema_generator.rb +6 -0
  44. data/lib/sequel/database/schema_methods.rb +5 -5
  45. data/lib/sequel/database/schema_sql.rb +1 -1
  46. data/lib/sequel/dataset.rb +4 -190
  47. data/lib/sequel/dataset/actions.rb +323 -1
  48. data/lib/sequel/dataset/features.rb +18 -2
  49. data/lib/sequel/dataset/graph.rb +7 -0
  50. data/lib/sequel/dataset/misc.rb +119 -0
  51. data/lib/sequel/dataset/mutation.rb +64 -0
  52. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  53. data/lib/sequel/dataset/query.rb +272 -6
  54. data/lib/sequel/dataset/sql.rb +186 -394
  55. data/lib/sequel/model.rb +4 -2
  56. data/lib/sequel/model/associations.rb +31 -14
  57. data/lib/sequel/model/base.rb +32 -13
  58. data/lib/sequel/model/exceptions.rb +8 -4
  59. data/lib/sequel/model/plugins.rb +3 -13
  60. data/lib/sequel/plugins/active_model.rb +26 -7
  61. data/lib/sequel/plugins/instance_filters.rb +98 -0
  62. data/lib/sequel/plugins/many_through_many.rb +1 -1
  63. data/lib/sequel/plugins/optimistic_locking.rb +25 -9
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +26 -0
  66. data/spec/adapters/mysql_spec.rb +33 -4
  67. data/spec/adapters/postgres_spec.rb +24 -1
  68. data/spec/adapters/spec_helper.rb +6 -0
  69. data/spec/adapters/sqlite_spec.rb +28 -0
  70. data/spec/core/connection_pool_spec.rb +17 -5
  71. data/spec/core/database_spec.rb +101 -1
  72. data/spec/core/dataset_spec.rb +42 -4
  73. data/spec/core/schema_spec.rb +13 -0
  74. data/spec/extensions/active_model_spec.rb +34 -11
  75. data/spec/extensions/caching_spec.rb +2 -0
  76. data/spec/extensions/instance_filters_spec.rb +55 -0
  77. data/spec/extensions/spec_helper.rb +2 -0
  78. data/spec/integration/dataset_test.rb +12 -1
  79. data/spec/integration/model_test.rb +12 -0
  80. data/spec/integration/plugin_test.rb +61 -1
  81. data/spec/integration/schema_test.rb +14 -3
  82. data/spec/model/base_spec.rb +27 -0
  83. data/spec/model/plugins_spec.rb +0 -22
  84. data/spec/model/record_spec.rb +32 -1
  85. data/spec/model/spec_helper.rb +2 -0
  86. metadata +14 -3
  87. data/lib/sequel/dataset/convenience.rb +0 -326
@@ -1,10 +1,26 @@
1
1
  module Sequel
2
2
  class Dataset
3
+ # ---------------------
4
+ # :section: Methods that describe what the dataset supports
5
+ # These methods all return booleans, with most describing whether or not the
6
+ # dataset supports a feature.
7
+ # ---------------------
8
+
9
+ # Method used to check if WITH is supported
10
+ WITH_SUPPORTED=:select_with_sql
11
+
3
12
  # Whether this dataset quotes identifiers.
4
13
  def quote_identifiers?
5
14
  @quote_identifiers
6
15
  end
7
16
 
17
+ # Whether this dataset will provide accurate number of rows matched for
18
+ # delete and update statements. Accurate in this case is the number of
19
+ # rows matched by the dataset's filter.
20
+ def provides_accurate_rows_matched?
21
+ true
22
+ end
23
+
8
24
  # Whether the dataset requires SQL standard datetimes (false by default,
9
25
  # as most allow strings with ISO 8601 format.
10
26
  def requires_sql_standard_datetimes?
@@ -16,9 +32,9 @@ module Sequel
16
32
  select_clause_methods.include?(WITH_SUPPORTED)
17
33
  end
18
34
 
19
- # Whether the dataset supports the DISTINCT ON clause, true by default.
35
+ # Whether the dataset supports the DISTINCT ON clause, false by default.
20
36
  def supports_distinct_on?
21
- true
37
+ false
22
38
  end
23
39
 
24
40
  # Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
@@ -1,5 +1,12 @@
1
1
  module Sequel
2
2
  class Dataset
3
+ # ---------------------
4
+ # :section: Methods related to dataset graphing
5
+ # Dataset graphing changes the dataset to yield hashes where keys are table
6
+ # name symbols and columns are hashes representing the values related to
7
+ # that table. All of these methods return modified copies of the receiver.
8
+ # ---------------------
9
+
3
10
  # Adds the given graph aliases to the list of graph aliases to use,
4
11
  # unlike #set_graph_aliases, which replaces the list. See
5
12
  # #set_graph_aliases.
@@ -0,0 +1,119 @@
1
+ module Sequel
2
+ class Dataset
3
+ # ---------------------
4
+ # :section: Miscellaneous methods
5
+ # These methods don't fit cleanly into another section.
6
+ # ---------------------
7
+
8
+ NOTIMPL_MSG = "This method must be overridden in Sequel adapters".freeze
9
+ ARRAY_ACCESS_ERROR_MSG = 'You cannot call Dataset#[] with an integer or with no arguments.'.freeze
10
+ ARG_BLOCK_ERROR_MSG = 'Must use either an argument or a block, not both'.freeze
11
+ IMPORT_ERROR_MSG = 'Using Sequel::Dataset#import an empty column array is not allowed'.freeze
12
+
13
+ # The database that corresponds to this dataset
14
+ attr_accessor :db
15
+
16
+ # The hash of options for this dataset, keys are symbols.
17
+ attr_accessor :opts
18
+
19
+ # Constructs a new Dataset instance with an associated database and
20
+ # options. Datasets are usually constructed by invoking the Database#[] method:
21
+ #
22
+ # DB[:posts]
23
+ #
24
+ # Sequel::Dataset is an abstract class that is not useful by itself. Each
25
+ # database adaptor should provide a subclass of Sequel::Dataset, and have
26
+ # the Database#dataset method return an instance of that class.
27
+ def initialize(db, opts = nil)
28
+ @db = db
29
+ @quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
30
+ @identifier_input_method = db.identifier_input_method if db.respond_to?(:identifier_input_method)
31
+ @identifier_output_method = db.identifier_output_method if db.respond_to?(:identifier_output_method)
32
+ @opts = opts || {}
33
+ @row_proc = nil
34
+ end
35
+
36
+ # Return the dataset as an aliased expression with the given alias. You can
37
+ # use this as a FROM or JOIN dataset, or as a column if this dataset
38
+ # returns a single row and column.
39
+ def as(aliaz)
40
+ ::Sequel::SQL::AliasedExpression.new(self, aliaz)
41
+ end
42
+
43
+ # Yield a dataset for each server in the connection pool that is tied to that server.
44
+ # Intended for use in sharded environments where all servers need to be modified
45
+ # with the same data:
46
+ #
47
+ # DB[:configs].where(:key=>'setting').each_server{|ds| ds.update(:value=>'new_value')}
48
+ def each_server
49
+ db.servers.each{|s| yield server(s)}
50
+ end
51
+
52
+ # The first source (primary table) for this dataset. If the dataset doesn't
53
+ # have a table, raises an error. If the table is aliased, returns the aliased name.
54
+ def first_source_alias
55
+ source = @opts[:from]
56
+ if source.nil? || source.empty?
57
+ raise Error, 'No source specified for query'
58
+ end
59
+ case s = source.first
60
+ when SQL::AliasedExpression
61
+ s.aliaz
62
+ when Symbol
63
+ sch, table, aliaz = split_symbol(s)
64
+ aliaz ? aliaz.to_sym : s
65
+ else
66
+ s
67
+ end
68
+ end
69
+ alias first_source first_source_alias
70
+
71
+ # The first source (primary table) for this dataset. If the dataset doesn't
72
+ # have a table, raises an error. If the table is aliased, returns the original
73
+ # table, not the alias
74
+ def first_source_table
75
+ source = @opts[:from]
76
+ if source.nil? || source.empty?
77
+ raise Error, 'No source specified for query'
78
+ end
79
+ case s = source.first
80
+ when SQL::AliasedExpression
81
+ s.expression
82
+ when Symbol
83
+ sch, table, aliaz = split_symbol(s)
84
+ aliaz ? (sch ? SQL::QualifiedIdentifier.new(sch, table) : table.to_sym) : s
85
+ else
86
+ s
87
+ end
88
+ end
89
+
90
+ # Returns a string representation of the dataset including the class name
91
+ # and the corresponding SQL select statement.
92
+ def inspect
93
+ "#<#{self.class}: #{sql.inspect}>"
94
+ end
95
+
96
+ # Creates a unique table alias that hasn't already been used in the dataset.
97
+ # table_alias can be any type of object accepted by alias_symbol.
98
+ # The symbol returned will be the implicit alias in the argument,
99
+ # possibly appended with "_N" if the implicit alias has already been
100
+ # used, where N is an integer starting at 0 and increasing until an
101
+ # unused one is found.
102
+ def unused_table_alias(table_alias)
103
+ table_alias = alias_symbol(table_alias)
104
+ used_aliases = []
105
+ used_aliases += opts[:from].map{|t| alias_symbol(t)} if opts[:from]
106
+ used_aliases += opts[:join].map{|j| j.table_alias ? alias_alias_symbol(j.table_alias) : alias_symbol(j.table)} if opts[:join]
107
+ if used_aliases.include?(table_alias)
108
+ i = 0
109
+ loop do
110
+ ta = :"#{table_alias}_#{i}"
111
+ return ta unless used_aliases.include?(ta)
112
+ i += 1
113
+ end
114
+ else
115
+ table_alias
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,64 @@
1
+ module Sequel
2
+ class Dataset
3
+ # ---------------------
4
+ # :section: Mutation methods
5
+ # These methods modify the receiving dataset and should be used with care.
6
+ # ---------------------
7
+
8
+ # All methods that should have a ! method added that modifies
9
+ # the receiver.
10
+ MUTATION_METHODS = %w'add_graph_aliases and cross_join distinct except exclude
11
+ filter for_update from from_self full_join full_outer_join graph
12
+ group group_and_count group_by having inner_join intersect invert join join_table left_join
13
+ left_outer_join limit lock_style naked natural_full_join natural_join
14
+ natural_left_join natural_right_join or order order_by order_more paginate qualify query
15
+ reverse reverse_order right_join right_outer_join select select_all select_append select_more server
16
+ set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
17
+ unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym}
18
+
19
+ # Setup mutation (e.g. filter!) methods. These operate the same as the
20
+ # non-! methods, but replace the options of the current dataset with the
21
+ # options of the resulting dataset.
22
+ def self.def_mutation_method(*meths)
23
+ meths.each do |meth|
24
+ class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
25
+ end
26
+ end
27
+
28
+ # Add the mutation methods via metaprogramming
29
+ def_mutation_method(*MUTATION_METHODS)
30
+
31
+
32
+ # Set the method to call on identifiers going into the database for this dataset
33
+ attr_accessor :identifier_input_method
34
+
35
+ # Set the method to call on identifiers coming the database for this dataset
36
+ attr_accessor :identifier_output_method
37
+
38
+ # Whether to quote identifiers for this dataset
39
+ attr_writer :quote_identifiers
40
+
41
+ # The row_proc for this database, should be a Proc that takes
42
+ # a single hash argument and returns the object you want
43
+ # each to return.
44
+ attr_accessor :row_proc
45
+
46
+ # Add a mutation method to this dataset instance.
47
+ def def_mutation_method(*meths)
48
+ meths.each do |meth|
49
+ instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Modify the receiver with the results of sending the meth, args, and block
56
+ # to the receiver and merging the options of the resulting dataset into
57
+ # the receiver's options.
58
+ def mutation_method(meth, *args, &block)
59
+ copy = send(meth, *args, &block)
60
+ @opts.merge!(copy.opts)
61
+ self
62
+ end
63
+ end
64
+ end
@@ -1,5 +1,11 @@
1
1
  module Sequel
2
2
  class Dataset
3
+ # ---------------------
4
+ # :section: Methods related to prepared statements or bound variables
5
+ # On some adapters, these use native prepared statements and bound variables, on others
6
+ # support is emulated. For details, see the {"Prepared Statements/Bound Variables" guide}[link:files/doc/prepared_statements_rdoc.html].
7
+ # ---------------------
8
+
3
9
  PREPARED_ARG_PLACEHOLDER = LiteralString.new('?').freeze
4
10
 
5
11
  # Default implementation of the argument mapper to allow
@@ -1,5 +1,28 @@
1
1
  module Sequel
2
2
  class Dataset
3
+ # ---------------------
4
+ # :section: Methods that return modified datasets
5
+ # These methods all return modified copies of the receiver.
6
+ # ---------------------
7
+ # The dataset options that require the removal of cached columns
8
+ # if changed.
9
+ COLUMN_CHANGE_OPTS = [:select, :sql, :from, :join].freeze
10
+
11
+ # Which options don't affect the SQL generation. Used by simple_select_all?
12
+ # to determine if this is a simple SELECT * FROM table.
13
+ NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager_graph, :graph_aliases]
14
+
15
+ # These symbols have _join methods created (e.g. inner_join) that
16
+ # call join_table with the symbol, passing along the arguments and
17
+ # block from the method call.
18
+ CONDITIONED_JOIN_TYPES = [:inner, :full_outer, :right_outer, :left_outer, :full, :right, :left]
19
+
20
+ # These symbols have _join methods created (e.g. natural_join) that
21
+ # call join_table with the symbol. They only accept a single table
22
+ # argument which is passed to join_table, and they raise an error
23
+ # if called with a block.
24
+ UNCONDITIONED_JOIN_TYPES = [:natural, :natural_left, :natural_right, :natural_full, :cross]
25
+
3
26
  # Adds an further filter to an existing filter using AND. If no filter
4
27
  # exists an error is raised. This method is identical to #filter except
5
28
  # it expects an existing filter.
@@ -9,6 +32,16 @@ module Sequel
9
32
  raise(InvalidOperation, "No existing filter found.") unless @opts[:having] || @opts[:where]
10
33
  filter(*cond, &block)
11
34
  end
35
+
36
+ # Returns a new clone of the dataset with with the given options merged.
37
+ # If the options changed include options in COLUMN_CHANGE_OPTS, the cached
38
+ # columns are deleted.
39
+ def clone(opts = {})
40
+ c = super()
41
+ c.opts = @opts.merge(opts)
42
+ c.instance_variable_set(:@columns, nil) if opts.keys.any?{|o| COLUMN_CHANGE_OPTS.include?(o)}
43
+ c
44
+ end
12
45
 
13
46
  # Returns a copy of the dataset with the SQL DISTINCT clause.
14
47
  # The DISTINCT clause is used to remove duplicate rows from the
@@ -74,7 +107,8 @@ module Sequel
74
107
  #
75
108
  # filter also takes a block, which should return one of the above argument
76
109
  # types, and is treated the same way. This block yields a virtual row object,
77
- # which is easy to use to create identifiers and functions.
110
+ # which is easy to use to create identifiers and functions. For more details
111
+ # on the virtual row support, see the {"Virtual Rows" guide}[link:files/doc/virtual_rows_rdoc.html]
78
112
  #
79
113
  # If both a block and regular argument
80
114
  # are provided, they get ANDed together.
@@ -100,7 +134,7 @@ module Sequel
100
134
  # software.filter{|o| o.price < 100}.sql #=>
101
135
  # "SELECT * FROM items WHERE ((category = 'software') AND (price < 100))"
102
136
  #
103
- # See doc/dataset_filtering.rdoc for more examples and details.
137
+ # See the the {"Dataset Filtering" guide}[link:files/doc/dataset_filtering_rdoc.html] for more examples and details.
104
138
  def filter(*cond, &block)
105
139
  _filter(@opts[:having] ? :having : :where, *cond, &block)
106
140
  end
@@ -175,6 +209,19 @@ module Sequel
175
209
  clone(:group => (columns.compact.empty? ? nil : columns))
176
210
  end
177
211
  alias group_by group
212
+
213
+ # Returns a dataset grouped by the given column with count by group,
214
+ # order by the count of records. Column aliases may be supplied, and will
215
+ # be included in the select clause.
216
+ #
217
+ # Examples:
218
+ #
219
+ # ds.group_and_count(:name).all => [{:name=>'a', :count=>1}, ...]
220
+ # ds.group_and_count(:first_name, :last_name).all => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
221
+ # ds.group_and_count(:first_name___name).all => [{:name=>'a', :count=>1}, ...]
222
+ def group_and_count(*columns)
223
+ group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
224
+ end
178
225
 
179
226
  # Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
180
227
  #
@@ -213,6 +260,100 @@ module Sequel
213
260
  clone(o)
214
261
  end
215
262
 
263
+ # Returns a joined dataset. Uses the following arguments:
264
+ #
265
+ # * type - The type of join to do (e.g. :inner)
266
+ # * table - Depends on type:
267
+ # * Dataset - a subselect is performed with an alias of tN for some value of N
268
+ # * Model (or anything responding to :table_name) - table.table_name
269
+ # * String, Symbol: table
270
+ # * expr - specifies conditions, depends on type:
271
+ # * Hash, Array with all two pairs - Assumes key (1st arg) is column of joined table (unless already
272
+ # qualified), and value (2nd arg) is column of the last joined or primary table (or the
273
+ # :implicit_qualifier option).
274
+ # To specify multiple conditions on a single joined table column, you must use an array.
275
+ # Uses a JOIN with an ON clause.
276
+ # * Array - If all members of the array are symbols, considers them as columns and
277
+ # uses a JOIN with a USING clause. Most databases will remove duplicate columns from
278
+ # the result set if this is used.
279
+ # * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
280
+ # or CROSS join. If a block is given, uses a ON clause based on the block, see below.
281
+ # * Everything else - pretty much the same as a using the argument in a call to filter,
282
+ # so strings are considered literal, symbols specify boolean columns, and blockless
283
+ # filter expressions can be used. Uses a JOIN with an ON clause.
284
+ # * options - a hash of options, with any of the following keys:
285
+ # * :table_alias - the name of the table's alias when joining, necessary for joining
286
+ # to the same table more than once. No alias is used by default.
287
+ # * :implicit_qualifier - The name to use for qualifying implicit conditions. By default,
288
+ # the last joined or primary table is used.
289
+ # * block - The block argument should only be given if a JOIN with an ON clause is used,
290
+ # in which case it yields the table alias/name for the table currently being joined,
291
+ # the table alias/name for the last joined (or first table), and an array of previous
292
+ # SQL::JoinClause.
293
+ def join_table(type, table, expr=nil, options={}, &block)
294
+ using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
295
+ if using_join && !supports_join_using?
296
+ h = {}
297
+ expr.each{|s| h[s] = s}
298
+ return join_table(type, table, h, options)
299
+ end
300
+
301
+ case options
302
+ when Hash
303
+ table_alias = options[:table_alias]
304
+ last_alias = options[:implicit_qualifier]
305
+ when Symbol, String, SQL::Identifier
306
+ table_alias = options
307
+ last_alias = nil
308
+ else
309
+ raise Error, "invalid options format for join_table: #{options.inspect}"
310
+ end
311
+
312
+ if Dataset === table
313
+ if table_alias.nil?
314
+ table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
315
+ table_alias = dataset_alias(table_alias_num)
316
+ end
317
+ table_name = table_alias
318
+ else
319
+ table = table.table_name if table.respond_to?(:table_name)
320
+ table_name = table_alias || table
321
+ end
322
+
323
+ join = if expr.nil? and !block_given?
324
+ SQL::JoinClause.new(type, table, table_alias)
325
+ elsif using_join
326
+ raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
327
+ SQL::JoinUsingClause.new(expr, type, table, table_alias)
328
+ else
329
+ last_alias ||= @opts[:last_joined_table] || first_source_alias
330
+ if Sequel.condition_specifier?(expr)
331
+ expr = expr.collect do |k, v|
332
+ k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
333
+ v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
334
+ [k,v]
335
+ end
336
+ end
337
+ if block_given?
338
+ expr2 = yield(table_name, last_alias, @opts[:join] || [])
339
+ expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
340
+ end
341
+ SQL::JoinOnClause.new(expr, type, table, table_alias)
342
+ end
343
+
344
+ opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
345
+ opts[:num_dataset_sources] = table_alias_num if table_alias_num
346
+ clone(opts)
347
+ end
348
+
349
+ CONDITIONED_JOIN_TYPES.each do |jtype|
350
+ class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end", __FILE__, __LINE__)
351
+ end
352
+ UNCONDITIONED_JOIN_TYPES.each do |jtype|
353
+ class_eval("def #{jtype}_join(table); raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?; join_table(:#{jtype}, table) end", __FILE__, __LINE__)
354
+ end
355
+ alias join inner_join
356
+
216
357
  # If given an integer, the dataset will contain only the first l results.
217
358
  # If given a range, it will contain only those at offsets within that
218
359
  # range. If a second argument is given, it is used as an offset.
@@ -249,6 +390,14 @@ module Sequel
249
390
  clone(:lock => style)
250
391
  end
251
392
 
393
+ # Returns a naked dataset clone - i.e. a dataset that returns records as
394
+ # hashes instead of calling the row proc.
395
+ def naked
396
+ ds = clone
397
+ ds.row_proc = nil
398
+ ds
399
+ end
400
+
252
401
  # Adds an alternate filter to an existing filter using OR. If no filter
253
402
  # exists an error is raised.
254
403
  #
@@ -289,6 +438,35 @@ module Sequel
289
438
  order(*columns, &block)
290
439
  end
291
440
 
441
+ # Qualify to the given table, or first source if not table is given.
442
+ def qualify(table=first_source)
443
+ qualify_to(table)
444
+ end
445
+
446
+ # Return a copy of the dataset with unqualified identifiers in the
447
+ # SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
448
+ # given table. If no columns are currently selected, select all
449
+ # columns of the given table.
450
+ def qualify_to(table)
451
+ o = @opts
452
+ return clone if o[:sql]
453
+ h = {}
454
+ (o.keys & QUALIFY_KEYS).each do |k|
455
+ h[k] = qualified_expression(o[k], table)
456
+ end
457
+ h[:select] = [SQL::ColumnAll.new(table)] if !o[:select] || o[:select].empty?
458
+ clone(h)
459
+ end
460
+
461
+ # Qualify the dataset to its current first source. This is useful
462
+ # if you have unqualified identifiers in the query that all refer to
463
+ # the first source, and you want to join to another table which
464
+ # has columns with the same name as columns in the current dataset.
465
+ # See qualify_to.
466
+ def qualify_to_first_source
467
+ qualify_to(first_source)
468
+ end
469
+
292
470
  # Returns a copy of the dataset with the order reversed. If no order is
293
471
  # given, the existing order is inverted.
294
472
  def reverse_order(*order)
@@ -318,17 +496,51 @@ module Sequel
318
496
  def select_all
319
497
  clone(:select => nil)
320
498
  end
499
+
500
+ # Returns a copy of the dataset with the given columns added
501
+ # to the existing selected columns. If no columns are currently selected
502
+ # it will select the columns given in addition to *.
503
+ #
504
+ # dataset.select(:a).select(:b) # SELECT b FROM items
505
+ # dataset.select(:a).select_append(:b) # SELECT a, b FROM items
506
+ # dataset.select_append(:b) # SELECT *, b FROM items
507
+ def select_append(*columns, &block)
508
+ cur_sel = @opts[:select]
509
+ cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
510
+ select(*(cur_sel + columns), &block)
511
+ end
321
512
 
322
513
  # Returns a copy of the dataset with the given columns added
323
- # to the existing selected columns.
514
+ # to the existing selected columns. If no columns are currently selected
515
+ # it will just select the columns given.
324
516
  #
325
517
  # dataset.select(:a).select(:b) # SELECT b FROM items
326
518
  # dataset.select(:a).select_more(:b) # SELECT a, b FROM items
519
+ # dataset.select_more(:b) # SELECT b FROM items
327
520
  def select_more(*columns, &block)
328
521
  columns = @opts[:select] + columns if @opts[:select]
329
522
  select(*columns, &block)
330
523
  end
331
524
 
525
+ # Set the server for this dataset to use. Used to pick a specific database
526
+ # shard to run a query against, or to override the default (which is SELECT uses
527
+ # :read_only database and all other queries use the :default database).
528
+ def server(servr)
529
+ clone(:server=>servr)
530
+ end
531
+
532
+ # Set the default values for insert and update statements. The values hash passed
533
+ # to insert or update are merged into this hash.
534
+ def set_defaults(hash)
535
+ clone(:defaults=>(@opts[:defaults]||{}).merge(hash))
536
+ end
537
+
538
+ # Set values that override hash arguments given to insert and update statements.
539
+ # This hash is merged into the hash provided to insert or update.
540
+ def set_overrides(hash)
541
+ clone(:overrides=>hash.merge(@opts[:overrides]||{}))
542
+ end
543
+
332
544
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
333
545
  #
334
546
  # dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items GROUP BY a
@@ -370,15 +582,69 @@ module Sequel
370
582
  def unordered
371
583
  order(nil)
372
584
  end
585
+
586
+ # Add a condition to the WHERE clause. See #filter for argument types.
587
+ #
588
+ # dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
589
+ # dataset.group(:a).having(:a).where(:b) # SELECT * FROM items WHERE b GROUP BY a HAVING a
590
+ def where(*cond, &block)
591
+ _filter(:where, *cond, &block)
592
+ end
593
+
594
+ # Add a simple common table expression (CTE) with the given name and a dataset that defines the CTE.
595
+ # A common table expression acts as an inline view for the query.
596
+ # Options:
597
+ # * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
598
+ # * :recursive - Specify that this is a recursive CTE
599
+ def with(name, dataset, opts={})
600
+ raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
601
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
602
+ end
603
+
604
+ # Add a recursive common table expression (CTE) with the given name, a dataset that
605
+ # defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
606
+ # of the CTE. Options:
607
+ # * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
608
+ # * :union_all - Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
609
+ def with_recursive(name, nonrecursive, recursive, opts={})
610
+ raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
611
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
612
+ end
613
+
614
+ # Returns a copy of the dataset with the static SQL used. This is useful if you want
615
+ # to keep the same row_proc/graph, but change the SQL used to custom SQL.
616
+ #
617
+ # dataset.with_sql('SELECT * FROM foo') # SELECT * FROM foo
618
+ def with_sql(sql, *args)
619
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
620
+ clone(:sql=>sql)
621
+ end
622
+
623
+ protected
624
+
625
+ # Return true if the dataset has a non-nil value for any key in opts.
626
+ def options_overlap(opts)
627
+ !(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
628
+ end
629
+
630
+ # Whether this dataset is a simple SELECT * FROM table.
631
+ def simple_select_all?
632
+ o = @opts.reject{|k,v| v.nil? || NON_SQL_OPTIONS.include?(k)}
633
+ o.length == 1 && (f = o[:from]) && f.length == 1 && f.first.is_a?(Symbol)
634
+ end
373
635
 
374
636
  private
375
637
 
376
638
  # Internal filter method so it works on either the having or where clauses.
377
639
  def _filter(clause, *cond, &block)
378
640
  cond = cond.first if cond.size == 1
379
- cond = filter_expr(cond, &block)
380
- cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
381
- clone(clause => cond)
641
+ if cond.respond_to?(:empty?) && cond.empty? && !block
642
+ clone
643
+ else
644
+ cond = filter_expr(cond, &block)
645
+ cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
646
+ clone(clause => cond)
647
+ end
382
648
  end
383
649
 
384
650
  # Add the dataset to the list of compounds