sequel 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. data/CHANGELOG +168 -0
  2. data/README.rdoc +77 -95
  3. data/Rakefile +100 -80
  4. data/bin/sequel +2 -1
  5. data/doc/advanced_associations.rdoc +23 -32
  6. data/doc/cheat_sheet.rdoc +23 -40
  7. data/doc/dataset_filtering.rdoc +6 -6
  8. data/doc/prepared_statements.rdoc +22 -22
  9. data/doc/release_notes/2.12.0.txt +534 -0
  10. data/doc/schema.rdoc +3 -1
  11. data/doc/sharding.rdoc +8 -8
  12. data/doc/virtual_rows.rdoc +65 -0
  13. data/lib/sequel.rb +1 -1
  14. data/lib/{sequel_core → sequel}/adapters/ado.rb +3 -3
  15. data/lib/{sequel_core → sequel}/adapters/db2.rb +0 -0
  16. data/lib/{sequel_core → sequel}/adapters/dbi.rb +1 -1
  17. data/lib/{sequel_core → sequel}/adapters/do.rb +9 -5
  18. data/lib/{sequel_core → sequel}/adapters/do/mysql.rb +1 -1
  19. data/lib/{sequel_core → sequel}/adapters/do/postgres.rb +1 -1
  20. data/lib/{sequel_core → sequel}/adapters/do/sqlite.rb +1 -1
  21. data/lib/{sequel_core → sequel}/adapters/firebird.rb +84 -80
  22. data/lib/{sequel_core → sequel}/adapters/informix.rb +1 -1
  23. data/lib/{sequel_core → sequel}/adapters/jdbc.rb +21 -14
  24. data/lib/{sequel_core → sequel}/adapters/jdbc/h2.rb +14 -13
  25. data/lib/{sequel_core → sequel}/adapters/jdbc/mysql.rb +1 -1
  26. data/lib/{sequel_core → sequel}/adapters/jdbc/oracle.rb +1 -1
  27. data/lib/{sequel_core → sequel}/adapters/jdbc/postgresql.rb +1 -1
  28. data/lib/{sequel_core → sequel}/adapters/jdbc/sqlite.rb +1 -1
  29. data/lib/{sequel_core → sequel}/adapters/mysql.rb +60 -39
  30. data/lib/{sequel_core → sequel}/adapters/odbc.rb +8 -4
  31. data/lib/{sequel_core → sequel}/adapters/openbase.rb +0 -0
  32. data/lib/{sequel_core → sequel}/adapters/oracle.rb +38 -7
  33. data/lib/{sequel_core → sequel}/adapters/postgres.rb +24 -24
  34. data/lib/{sequel_core → sequel}/adapters/shared/mssql.rb +5 -5
  35. data/lib/{sequel_core → sequel}/adapters/shared/mysql.rb +126 -71
  36. data/lib/{sequel_core → sequel}/adapters/shared/oracle.rb +7 -10
  37. data/lib/{sequel_core → sequel}/adapters/shared/postgres.rb +159 -125
  38. data/lib/{sequel_core → sequel}/adapters/shared/progress.rb +1 -2
  39. data/lib/{sequel_core → sequel}/adapters/shared/sqlite.rb +72 -67
  40. data/lib/{sequel_core → sequel}/adapters/sqlite.rb +11 -7
  41. data/lib/{sequel_core → sequel}/adapters/utils/date_format.rb +0 -0
  42. data/lib/{sequel_core → sequel}/adapters/utils/stored_procedures.rb +0 -0
  43. data/lib/{sequel_core → sequel}/adapters/utils/unsupported.rb +19 -0
  44. data/lib/{sequel_core → sequel}/connection_pool.rb +7 -5
  45. data/lib/sequel/core.rb +221 -0
  46. data/lib/{sequel_core → sequel}/core_sql.rb +91 -49
  47. data/lib/{sequel_core → sequel}/database.rb +264 -149
  48. data/lib/{sequel_core/schema/generator.rb → sequel/database/schema_generator.rb} +6 -2
  49. data/lib/{sequel_core/database/schema.rb → sequel/database/schema_methods.rb} +12 -12
  50. data/lib/sequel/database/schema_sql.rb +224 -0
  51. data/lib/{sequel_core → sequel}/dataset.rb +78 -236
  52. data/lib/{sequel_core → sequel}/dataset/convenience.rb +99 -61
  53. data/lib/{sequel_core/object_graph.rb → sequel/dataset/graph.rb} +16 -14
  54. data/lib/{sequel_core → sequel}/dataset/prepared_statements.rb +1 -1
  55. data/lib/{sequel_core → sequel}/dataset/sql.rb +150 -99
  56. data/lib/sequel/deprecated.rb +593 -0
  57. data/lib/sequel/deprecated_migration.rb +91 -0
  58. data/lib/sequel/exceptions.rb +48 -0
  59. data/lib/sequel/extensions/blank.rb +42 -0
  60. data/lib/{sequel_model → sequel/extensions}/inflector.rb +8 -1
  61. data/lib/{sequel_core → sequel/extensions}/migration.rb +1 -1
  62. data/lib/{sequel_core/dataset → sequel/extensions}/pagination.rb +0 -0
  63. data/lib/{sequel_core → sequel/extensions}/pretty_table.rb +7 -0
  64. data/lib/{sequel_core/dataset → sequel/extensions}/query.rb +7 -0
  65. data/lib/sequel/extensions/string_date_time.rb +47 -0
  66. data/lib/sequel/metaprogramming.rb +43 -0
  67. data/lib/sequel/model.rb +110 -0
  68. data/lib/sequel/model/associations.rb +1300 -0
  69. data/lib/sequel/model/base.rb +937 -0
  70. data/lib/sequel/model/deprecated.rb +204 -0
  71. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  72. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  73. data/lib/sequel/model/deprecated_validations.rb +388 -0
  74. data/lib/sequel/model/errors.rb +39 -0
  75. data/lib/{sequel_model → sequel/model}/exceptions.rb +4 -4
  76. data/lib/sequel/model/inflections.rb +208 -0
  77. data/lib/sequel/model/plugins.rb +76 -0
  78. data/lib/sequel/plugins/caching.rb +122 -0
  79. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  80. data/lib/sequel/plugins/schema.rb +53 -0
  81. data/lib/sequel/plugins/serialization.rb +117 -0
  82. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  83. data/lib/sequel/plugins/validation_class_methods.rb +384 -0
  84. data/lib/sequel/plugins/validation_helpers.rb +150 -0
  85. data/lib/{sequel_core → sequel}/sql.rb +125 -190
  86. data/lib/{sequel_core → sequel}/version.rb +2 -1
  87. data/lib/sequel_core.rb +1 -172
  88. data/lib/sequel_model.rb +1 -91
  89. data/spec/adapters/firebird_spec.rb +5 -5
  90. data/spec/adapters/informix_spec.rb +1 -1
  91. data/spec/adapters/mysql_spec.rb +128 -42
  92. data/spec/adapters/oracle_spec.rb +47 -19
  93. data/spec/adapters/postgres_spec.rb +64 -52
  94. data/spec/adapters/spec_helper.rb +1 -1
  95. data/spec/adapters/sqlite_spec.rb +12 -17
  96. data/spec/{sequel_core → core}/connection_pool_spec.rb +10 -10
  97. data/spec/{sequel_core → core}/core_ext_spec.rb +19 -19
  98. data/spec/{sequel_core → core}/core_sql_spec.rb +68 -71
  99. data/spec/{sequel_core → core}/database_spec.rb +135 -99
  100. data/spec/{sequel_core → core}/dataset_spec.rb +398 -242
  101. data/spec/{sequel_core → core}/expression_filters_spec.rb +13 -13
  102. data/spec/core/migration_spec.rb +263 -0
  103. data/spec/{sequel_core → core}/object_graph_spec.rb +10 -10
  104. data/spec/{sequel_core → core}/pretty_table_spec.rb +2 -2
  105. data/spec/{sequel_core → core}/schema_generator_spec.rb +0 -0
  106. data/spec/{sequel_core → core}/schema_spec.rb +8 -10
  107. data/spec/{sequel_core → core}/spec_helper.rb +29 -2
  108. data/spec/{sequel_core → core}/version_spec.rb +0 -0
  109. data/spec/extensions/blank_spec.rb +67 -0
  110. data/spec/extensions/caching_spec.rb +201 -0
  111. data/spec/{sequel_model/hooks_spec.rb → extensions/hook_class_methods_spec.rb} +8 -23
  112. data/spec/{sequel_model → extensions}/inflector_spec.rb +3 -0
  113. data/spec/{sequel_core → extensions}/migration_spec.rb +4 -4
  114. data/spec/extensions/pagination_spec.rb +99 -0
  115. data/spec/extensions/pretty_table_spec.rb +91 -0
  116. data/spec/extensions/query_spec.rb +85 -0
  117. data/spec/{sequel_model → extensions}/schema_spec.rb +22 -1
  118. data/spec/extensions/serialization_spec.rb +109 -0
  119. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  120. data/spec/{sequel_model → extensions}/spec_helper.rb +13 -4
  121. data/spec/extensions/string_date_time_spec.rb +93 -0
  122. data/spec/{sequel_model/validations_spec.rb → extensions/validation_class_methods_spec.rb} +15 -103
  123. data/spec/extensions/validation_helpers_spec.rb +291 -0
  124. data/spec/integration/dataset_test.rb +31 -0
  125. data/spec/integration/eager_loader_test.rb +17 -30
  126. data/spec/integration/schema_test.rb +8 -5
  127. data/spec/integration/spec_helper.rb +17 -0
  128. data/spec/integration/transaction_test.rb +68 -0
  129. data/spec/{sequel_model → model}/association_reflection_spec.rb +0 -0
  130. data/spec/{sequel_model → model}/associations_spec.rb +23 -10
  131. data/spec/{sequel_model → model}/base_spec.rb +29 -20
  132. data/spec/{sequel_model → model}/caching_spec.rb +16 -14
  133. data/spec/{sequel_model → model}/dataset_methods_spec.rb +0 -0
  134. data/spec/{sequel_model → model}/eager_loading_spec.rb +8 -8
  135. data/spec/model/hooks_spec.rb +472 -0
  136. data/spec/model/inflector_spec.rb +126 -0
  137. data/spec/{sequel_model → model}/model_spec.rb +25 -20
  138. data/spec/model/plugins_spec.rb +142 -0
  139. data/spec/{sequel_model → model}/record_spec.rb +121 -62
  140. data/spec/model/schema_spec.rb +92 -0
  141. data/spec/model/spec_helper.rb +124 -0
  142. data/spec/model/validations_spec.rb +1080 -0
  143. metadata +136 -107
  144. data/lib/sequel_core/core_ext.rb +0 -217
  145. data/lib/sequel_core/dataset/callback.rb +0 -13
  146. data/lib/sequel_core/dataset/schema.rb +0 -15
  147. data/lib/sequel_core/deprecated.rb +0 -26
  148. data/lib/sequel_core/exceptions.rb +0 -44
  149. data/lib/sequel_core/schema.rb +0 -2
  150. data/lib/sequel_core/schema/sql.rb +0 -325
  151. data/lib/sequel_model/association_reflection.rb +0 -267
  152. data/lib/sequel_model/associations.rb +0 -499
  153. data/lib/sequel_model/base.rb +0 -539
  154. data/lib/sequel_model/caching.rb +0 -82
  155. data/lib/sequel_model/dataset_methods.rb +0 -26
  156. data/lib/sequel_model/eager_loading.rb +0 -370
  157. data/lib/sequel_model/hooks.rb +0 -101
  158. data/lib/sequel_model/plugins.rb +0 -62
  159. data/lib/sequel_model/record.rb +0 -568
  160. data/lib/sequel_model/schema.rb +0 -49
  161. data/lib/sequel_model/validations.rb +0 -429
  162. data/spec/sequel_model/plugins_spec.rb +0 -80
@@ -3,13 +3,19 @@ module Sequel
3
3
  COMMA_SEPARATOR = ', '.freeze
4
4
  COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
5
5
 
6
- # Returns the first record matching the conditions.
6
+ # Returns the first record matching the conditions. Examples:
7
+ #
8
+ # ds[:id=>1] => {:id=1}
7
9
  def [](*conditions)
10
+ Deprecation.deprecate('Using an Integer argument to Dataset#[] is deprecated and will raise an error in Sequel 3.0. Use Dataset#first.') if conditions.length == 1 and conditions.is_a?(Integer)
11
+ Deprecation.deprecate('Using Dataset#[] without an argument is deprecated and will raise an error in Sequel 3.0. Use Dataset#first.') if conditions.length == 0
8
12
  first(*conditions)
9
13
  end
10
14
 
11
15
  # Update all records matching the conditions
12
- # with the values specified.
16
+ # with the values specified. Examples:
17
+ #
18
+ # ds[:id=>1] = {:id=>2} # SQL: UPDATE ... SET id = 2 WHERE id = 1
13
19
  def []=(conditions, values)
14
20
  filter(conditions).update(values)
15
21
  end
@@ -19,20 +25,18 @@ module Sequel
19
25
  get{|o| o.avg(column)}
20
26
  end
21
27
 
22
- # Returns true if no records exists in the dataset
28
+ # Returns true if no records exist in the dataset, false otherwise
23
29
  def empty?
24
30
  get(1).nil?
25
31
  end
26
32
 
27
- # Returns the first record in the dataset. If a numeric argument is
33
+ # If a integer argument is
28
34
  # given, it is interpreted as a limit, and then returns all
29
35
  # matching records up to that limit. If no argument is passed,
30
36
  # it returns the first matching record. If any other type of
31
37
  # argument(s) is passed, it is given to filter and the
32
38
  # first matching record is returned. If a block is given, it is used
33
- # to filter the dataset before returning anything.
34
- #
35
- # Examples:
39
+ # to filter the dataset before returning anything. Examples:
36
40
  #
37
41
  # ds.first => {:id=>7}
38
42
  # ds.first(2) => [{:id=>6}, {:id=>4}]
@@ -61,16 +65,74 @@ module Sequel
61
65
  end
62
66
 
63
67
  # Return the column value for the first matching record in the dataset.
68
+ # Raises an error if both an argument and block is given.
69
+ #
70
+ # ds.get(:id)
71
+ # ds.get{|o| o.sum(:id)}
64
72
  def get(column=nil, &block)
65
73
  raise(Error, 'must provide argument or block to Dataset#get, not both') if column && block
66
74
  (column ? select(column) : select(&block)).single_value
67
75
  end
68
76
 
69
- # Returns a dataset grouped by the given column with count by group.
77
+ # Returns a dataset grouped by the given column with count by group,
78
+ # order by the count of records. Examples:
79
+ #
80
+ # ds.group_and_count(:name) => [{:name=>'a', :count=>1}, ...]
81
+ # ds.group_and_count(:first_name, :last_name) => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
70
82
  def group_and_count(*columns)
71
83
  group(*columns).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
72
84
  end
73
85
 
86
+ # Inserts multiple records into the associated table. This method can be
87
+ # to efficiently insert a large amounts of records into a table. Inserts
88
+ # are automatically wrapped in a transaction.
89
+ #
90
+ # This method is called with a columns array and an array of value arrays:
91
+ #
92
+ # dataset.import([:x, :y], [[1, 2], [3, 4]])
93
+ #
94
+ # This method also accepts a dataset instead of an array of value arrays:
95
+ #
96
+ # dataset.import([:x, :y], other_dataset.select(:a___x, :b___y))
97
+ #
98
+ # The method also accepts a :slice or :commit_every option that specifies
99
+ # the number of records to insert per transaction. This is useful especially
100
+ # when inserting a large number of records, e.g.:
101
+ #
102
+ # # this will commit every 50 records
103
+ # dataset.import([:x, :y], [[1, 2], [3, 4], ...], :slice => 50)
104
+ def import(*args)
105
+ if args.empty?
106
+ Sequel::Deprecation.deprecate('Calling Sequel::Dataset#import with no arguments', 'Use dataset.multi_insert([])')
107
+ return
108
+ elsif args[0].is_a?(Array) && args[1].is_a?(Array)
109
+ columns, values, opts = *args
110
+ elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
111
+ table = @opts[:from].first
112
+ columns, dataset = *args
113
+ sql = "INSERT INTO #{quote_identifier(table)} (#{identifier_list(columns)}) VALUES #{literal(dataset)}"
114
+ return @db.transaction{execute_dui(sql)}
115
+ else
116
+ Sequel::Deprecation.deprecate('Calling Sequel::Dataset#import with hashes', 'Use Sequel::Dataset#multi_insert')
117
+ return multi_insert(*args)
118
+ end
119
+ # make sure there's work to do
120
+ Sequel::Deprecation.deprecate('Calling Sequel::Dataset#import an empty column array is deprecated and will raise an error in Sequel 3.0.') if columns.empty?
121
+ return if columns.empty? || values.empty?
122
+
123
+ slice_size = opts && (opts[:commit_every] || opts[:slice])
124
+
125
+ if slice_size
126
+ values.each_slice(slice_size) do |slice|
127
+ statements = multi_insert_sql(columns, slice)
128
+ @db.transaction(opts){statements.each{|st| execute_dui(st)}}
129
+ end
130
+ else
131
+ statements = multi_insert_sql(columns, values)
132
+ @db.transaction{statements.each{|st| execute_dui(st)}}
133
+ end
134
+ end
135
+
74
136
  # Returns the interval between minimum and maximum values for the given
75
137
  # column.
76
138
  def interval(column)
@@ -87,10 +149,15 @@ module Sequel
87
149
  end
88
150
 
89
151
  # Maps column values for each record in the dataset (if a column name is
90
- # given), or performs the stock mapping functionality of Enumerable.
91
- def map(column_name = nil, &block)
92
- if column_name
93
- super() {|r| r[column_name]}
152
+ # given), or performs the stock mapping functionality of Enumerable.
153
+ # Raises an error if both an argument and block are given. Examples:
154
+ #
155
+ # ds.map(:id) => [1, 2, 3, ...]
156
+ # ds.map{|r| r[:id] * 2} => [2, 4, 6, ...]
157
+ def map(column=nil, &block)
158
+ Deprecation.deprecate('Using Dataset#map with an argument and a block is deprecated and will raise an error in Sequel 3.0. Use an argument or a block, not both.') if column && block
159
+ if column
160
+ super(){|r| r[column]}
94
161
  else
95
162
  super(&block)
96
163
  end
@@ -106,38 +173,23 @@ module Sequel
106
173
  get{|o| o.min(column)}
107
174
  end
108
175
 
109
- # Inserts multiple records into the associated table. This method can be
110
- # to efficiently insert a large amounts of records into a table. Inserts
111
- # are automatically wrapped in a transaction.
112
- #
113
- # This method should be called with a columns array and an array of value arrays:
114
- #
115
- # dataset.multi_insert([:x, :y], [[1, 2], [3, 4]])
116
- #
117
- # This method can also be called with an array of hashes:
176
+ # This is a front end for import that allows you to submit an array of
177
+ # hashes instead of arrays of columns and values:
118
178
  #
119
- # dataset.multi_insert({:x => 1}, {:x => 2})
179
+ # dataset.multi_insert([{:x => 1}, {:x => 2}])
120
180
  #
121
181
  # Be aware that all hashes should have the same keys if you use this calling method,
122
182
  # otherwise some columns could be missed or set to null instead of to default
123
183
  # values.
124
184
  #
125
- # The method also accepts a :slice or :commit_every option that specifies
126
- # the number of records to insert per transaction. This is useful especially
127
- # when inserting a large number of records, e.g.:
128
- #
129
- # # this will commit every 50 records
130
- # dataset.multi_insert(lots_of_records, :slice => 50)
185
+ # You can also use the :slice or :commit_every option that import accepts.
131
186
  def multi_insert(*args)
132
187
  if args.empty?
188
+ Sequel::Deprecation.deprecate('Calling Sequel::Dataset#multi_insert with no arguments', 'Use dataset.multi_insert([])')
133
189
  return
134
- elsif args[0].is_a?(Array) && args[1].is_a?(Array)
135
- columns, values, opts = *args
136
- elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
137
- table = @opts[:from].first
138
- columns, dataset = *args
139
- sql = "INSERT INTO #{quote_identifier(table)} (#{identifier_list(columns)}) VALUES #{literal(dataset)}"
140
- return @db.transaction{execute_dui(sql)}
190
+ elsif args[0].is_a?(Array) && (args[1].is_a?(Array) || args[1].is_a?(Dataset))
191
+ Sequel::Deprecation.deprecate('Calling Sequel::Dataset#multi_insert with an array of columns and an array of arrays of values', 'Use Sequel::Dataset#import')
192
+ return import(*args)
141
193
  else
142
194
  # we assume that an array of hashes is given
143
195
  hashes, opts = *args
@@ -146,28 +198,9 @@ module Sequel
146
198
  # convert the hashes into arrays
147
199
  values = hashes.map {|h| columns.map {|c| h[c]}}
148
200
  end
149
- # make sure there's work to do
150
- return if columns.empty? || values.empty?
151
-
152
- slice_size = opts && (opts[:commit_every] || opts[:slice])
153
-
154
- if slice_size
155
- values.each_slice(slice_size) do |slice|
156
- statements = multi_insert_sql(columns, slice)
157
- @db.transaction{statements.each{|st| execute_dui(st)}}
158
- end
159
- else
160
- statements = multi_insert_sql(columns, values)
161
- @db.transaction{statements.each{|st| execute_dui(st)}}
162
- end
163
- end
164
- alias_method :import, :multi_insert
165
-
166
- # Pretty prints the records in the dataset as plain-text table.
167
- def print(*cols)
168
- Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
201
+ import(columns, values, opts)
169
202
  end
170
-
203
+
171
204
  # Returns a Range object made from the minimum and maximum values for the
172
205
  # given column.
173
206
  def range(column)
@@ -177,15 +210,20 @@ module Sequel
177
210
  end
178
211
 
179
212
  # Returns the first record in the dataset.
180
- def single_record(opts = nil)
181
- each((opts||{}).merge(:limit=>1)){|r| return r}
213
+ def single_record(opts = (defarg=true;nil))
214
+ Deprecation.deprecate("Calling Dataset#single_record with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).single_record.") unless defarg
215
+ ds = clone(:limit=>1)
216
+ opts = opts.merge(:limit=>1) if opts and opts[:limit]
217
+ defarg ? ds.each{|r| return r} : ds.each(opts){|r| return r}
182
218
  nil
183
219
  end
184
220
 
185
221
  # Returns the first value of the first record in the dataset.
186
222
  # Returns nil if dataset is empty.
187
- def single_value(opts = nil)
188
- if r = single_record((opts||{}).merge(:graph=>false, :naked=>true))
223
+ def single_value(opts = (defarg=true;nil))
224
+ Deprecation.deprecate("Calling Dataset#single_value with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).single_value.") unless defarg
225
+ ds = naked.clone(:graph=>false)
226
+ if r = (defarg ? ds.single_record : ds.single_record(opts))
189
227
  r.values.first
190
228
  end
191
229
  end
@@ -213,7 +251,7 @@ module Sequel
213
251
  #
214
252
  # This does not use a CSV library or handle quoting of values in
215
253
  # any way. If any values in any of the rows could include commas or line
216
- # endings, you probably shouldn't use this.
254
+ # endings, you shouldn't use this.
217
255
  def to_csv(include_column_titles = true)
218
256
  n = naked
219
257
  cols = n.columns
@@ -9,7 +9,7 @@ module Sequel
9
9
  # # CREATE TABLE artists (id INTEGER, name TEXT);
10
10
  # # CREATE TABLE albums (id INTEGER, name TEXT, artist_id INTEGER);
11
11
  # DB[:artists].left_outer_join(:albums, :artist_id=>:id).first
12
- # => {:id=>(albums.id||artists.id), :name=>(albums.name||artist.names), :artist_id=>albums.artist_id}
12
+ # => {:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}
13
13
  # DB[:artists].graph(:albums, :artist_id=>:id).first
14
14
  # => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>{:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}}
15
15
  #
@@ -30,7 +30,7 @@ module Sequel
30
30
  #
31
31
  # Arguments:
32
32
  # * dataset - Can be a symbol (specifying a table), another dataset,
33
- # or an object that responds to .dataset and yields a symbol or a dataset
33
+ # or an object that responds to .dataset and return a symbol or a dataset
34
34
  # * join_conditions - Any condition(s) allowed by join_table.
35
35
  # * options - A hash of graph options. The following options are currently used:
36
36
  # * :implicit_qualifier - The qualifier of implicit conditions, see #join_table.
@@ -97,7 +97,7 @@ module Sequel
97
97
  select = opts[:select] = []
98
98
  columns.each do |column|
99
99
  column_aliases[column] = [master, column]
100
- select.push(column.qualify(master))
100
+ select.push(SQL::QualifiedIdentifier.new(master, column))
101
101
  end
102
102
  end
103
103
  end
@@ -129,9 +129,9 @@ module Sequel
129
129
  column_alias = :"#{column_alias}_#{column_alias_num}"
130
130
  ca_num[column_alias] += 1
131
131
  end
132
- [column_alias, column.qualify(table_alias).as(column_alias)]
132
+ [column_alias, SQL::QualifiedIdentifier.new(table_alias, column).as(column_alias)]
133
133
  else
134
- [column, column.qualify(table_alias)]
134
+ [column, SQL::QualifiedIdentifier.new(table_alias, column)]
135
135
  end
136
136
  column_aliases[col_alias] = [table_alias, column]
137
137
  select.push(identifier)
@@ -147,14 +147,16 @@ module Sequel
147
147
  # graphed dataset, and must be used instead of .select whenever
148
148
  # graphing is used. Example:
149
149
  #
150
- # DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name]).first
151
- # => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name}}
150
+ # DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name], :forty_two=>[:albums, :fourtwo, 42]).first
151
+ # => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name, :fourtwo=>42}}
152
152
  #
153
153
  # Arguments:
154
154
  # * graph_aliases - Should be a hash with keys being symbols of
155
- # column aliases, and values being arrays with two symbol elements.
156
- # The first element of the array should be the table alias,
157
- # and the second should be the actual column name.
155
+ # column aliases, and values being arrays with two or three elements.
156
+ # The first element of the array should be the table alias symbol,
157
+ # and the second should be the actual column name symbol. If the array
158
+ # has a third element, it is used as the value returned, instead of
159
+ # table_alias.column_name.
158
160
  def set_graph_aliases(graph_aliases)
159
161
  ds = select(*graph_alias_columns(graph_aliases))
160
162
  ds.opts[:graph_aliases] = graph_aliases
@@ -175,7 +177,7 @@ module Sequel
175
177
  # Transform the hash of graph aliases to an array of columns
176
178
  def graph_alias_columns(graph_aliases)
177
179
  graph_aliases.collect do |col_alias, tc|
178
- identifier = tc[2] || tc[1].qualify(tc[0])
180
+ identifier = tc[2] || SQL::QualifiedIdentifier.new(tc[0], tc[1])
179
181
  identifier = SQL::AliasedExpression.new(identifier, col_alias) if tc[2] or tc[1] != col_alias
180
182
  identifier
181
183
  end
@@ -184,7 +186,7 @@ module Sequel
184
186
  # Fetch the rows, split them into component table parts,
185
187
  # tranform and run the row_proc on each part (if applicable),
186
188
  # and yield a hash of the parts.
187
- def graph_each(opts, &block)
189
+ def graph_each(opts=(defarg=true;nil), &block)
188
190
  # Reject tables with nil datasets, as they are excluded from
189
191
  # the result set
190
192
  datasets = @opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
@@ -198,7 +200,7 @@ module Sequel
198
200
  # Use the manually set graph aliases, if any, otherwise
199
201
  # use the ones automatically created by .graph
200
202
  column_aliases = @opts[:graph_aliases] || @opts[:graph][:column_aliases]
201
- fetch_rows(select_sql(opts)) do |r|
203
+ fetch_rows(defarg ? select_sql : select_sql(opts)) do |r|
202
204
  graph = {}
203
205
  # Create the sub hashes, one per table
204
206
  table_aliases.each{|ta| graph[ta]={}}
@@ -213,7 +215,7 @@ module Sequel
213
215
  # row_proc if applicable
214
216
  datasets.each do |ta,ds,tr,rp|
215
217
  g = graph[ta]
216
- graph[ta] = if g.values.any?
218
+ graph[ta] = if g.values.any?{|x| !x.nil?}
217
219
  g = ds.transform_load(g) if tr
218
220
  g = rp[g] if rp
219
221
  g
@@ -77,7 +77,7 @@ module Sequel
77
77
  when :select, :all
78
78
  select_sql
79
79
  when :first
80
- select_sql(:limit=>1)
80
+ clone(:limit=>1).select_sql
81
81
  when :insert
82
82
  insert_sql(@prepared_modify_values)
83
83
  when :update
@@ -7,6 +7,8 @@ module Sequel
7
7
  COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
8
8
  COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
9
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
10
12
  N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
11
13
  NULL = "NULL".freeze
12
14
  QUESTION_MARK = '?'.freeze
@@ -18,8 +20,10 @@ module Sequel
18
20
  # Adds an further filter to an existing filter using AND. If no filter
19
21
  # exists an error is raised. This method is identical to #filter except
20
22
  # it expects an existing filter.
23
+ #
24
+ # ds.filter(:a).and(:b) # SQL: WHERE a AND b
21
25
  def and(*cond, &block)
22
- raise(Error::NoExistingFilter, "No existing filter found.") unless @opts[:having] || @opts[:where]
26
+ raise(InvalidOperation, "No existing filter found.") unless @opts[:having] || @opts[:where]
23
27
  filter(*cond, &block)
24
28
  end
25
29
 
@@ -56,6 +60,9 @@ module Sequel
56
60
  # SQL fragment for complex expressions
57
61
  def complex_expression_sql(op, args)
58
62
  case op
63
+ when *IS_OPERATORS
64
+ v = IS_LITERALS[args.at(1)] || raise(Error, 'Invalid argument used for IS operator')
65
+ "(#{literal(args.at(0))} #{op} #{v})"
59
66
  when *TWO_ARITY_OPERATORS
60
67
  "(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
61
68
  when *N_ARITY_OPERATORS
@@ -73,23 +80,23 @@ module Sequel
73
80
 
74
81
  # Returns the number of records in the dataset.
75
82
  def count
76
- options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count : single_value(STOCK_COUNT_OPTS).to_i
83
+ options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count : clone(STOCK_COUNT_OPTS).single_value.to_i
77
84
  end
78
- alias_method :size, :count
79
85
 
80
86
  # Formats a DELETE statement using the given options and dataset options.
81
87
  #
82
88
  # dataset.filter{|o| o.price >= 100}.delete_sql #=>
83
89
  # "DELETE FROM items WHERE (price >= 100)"
84
- def delete_sql(opts = nil)
90
+ def delete_sql(opts = (defarg=true;nil))
91
+ Deprecation.deprecate("Calling Dataset#delete_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).delete_sql.") unless defarg
85
92
  opts = opts ? @opts.merge(opts) : @opts
86
93
 
87
94
  return static_sql(opts[:sql]) if opts[:sql]
88
95
 
89
96
  if opts[:group]
90
- raise Error::InvalidOperation, "Grouped datasets cannot be deleted from"
97
+ raise InvalidOperation, "Grouped datasets cannot be deleted from"
91
98
  elsif opts[:from].is_a?(Array) && opts[:from].size > 1
92
- raise Error::InvalidOperation, "Joined datasets cannot be deleted from"
99
+ raise InvalidOperation, "Joined datasets cannot be deleted from"
93
100
  end
94
101
 
95
102
  sql = "DELETE FROM #{source_list(opts[:from])}"
@@ -101,6 +108,18 @@ module Sequel
101
108
  sql
102
109
  end
103
110
 
111
+ # Returns a copy of the dataset with the SQL DISTINCT clause.
112
+ # The DISTINCT clause is used to remove duplicate rows from the
113
+ # output. If arguments are provided, uses a DISTINCT ON clause,
114
+ # in which case it will only be distinct on those columns, instead
115
+ # of all returned columns.
116
+ #
117
+ # dataset.distinct # SQL: SELECT DISTINCT * FROM items
118
+ # dataset.order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
119
+ def distinct(*args)
120
+ clone(:distinct => args)
121
+ end
122
+
104
123
  # Adds an EXCEPT clause using a second dataset object. If all is true the
105
124
  # clause used is EXCEPT ALL, which may return duplicate rows.
106
125
  #
@@ -117,7 +136,7 @@ module Sequel
117
136
  def exclude(*cond, &block)
118
137
  clause = (@opts[:having] ? :having : :where)
119
138
  cond = cond.first if cond.size == 1
120
- cond = cond.sql_or if (Hash === cond) || ((Array === cond) && (cond.all_two_pairs?))
139
+ cond = SQL::BooleanExpression.from_value_pairs(cond, :OR) if Sequel.condition_specifier?(cond)
121
140
  cond = filter_expr(cond, &block)
122
141
  cond = SQL::BooleanExpression.invert(cond)
123
142
  cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
@@ -128,17 +147,18 @@ module Sequel
128
147
  #
129
148
  # DB.select(1).where(DB[:items].exists).sql
130
149
  # #=> "SELECT 1 WHERE EXISTS (SELECT * FROM items)"
131
- def exists(opts = nil)
132
- LiteralString.new("EXISTS (#{select_sql(opts)})")
150
+ def exists(opts = (defarg=true;nil))
151
+ Deprecation.deprecate("Calling Dataset#exists with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).exists.") unless defarg
152
+ LiteralString.new("EXISTS (#{defarg ? select_sql : select_sql(opts)})")
133
153
  end
134
154
 
135
155
  # Returns a copy of the dataset with the given conditions imposed upon it.
136
- # If the query has been grouped, then the conditions are imposed in the
137
- # HAVING clause. If not, then they are imposed in the WHERE clause. Filter
156
+ # If the query already has a HAVING clause, then the conditions are imposed in the
157
+ # HAVING clause. If not, then they are imposed in the WHERE clause.
138
158
  #
139
159
  # filter accepts the following argument types:
140
160
  #
141
- # * Hash - list of equality expressions
161
+ # * Hash - list of equality/inclusion expressions
142
162
  # * Array - depends:
143
163
  # * If first member is a string, assumes the rest of the arguments
144
164
  # are parameters and interpolates them into the string.
@@ -148,10 +168,13 @@ module Sequel
148
168
  # * String - taken literally
149
169
  # * Symbol - taken as a boolean column argument (e.g. WHERE active)
150
170
  # * Sequel::SQL::BooleanExpression - an existing condition expression,
151
- # probably created using the Sequel blockless filter DSL.
171
+ # probably created using the Sequel expression filter DSL.
152
172
  #
153
173
  # filter also takes a block, which should return one of the above argument
154
- # types, and is treated the same way. If both a block and regular argument
174
+ # types, and is treated the same way. This block yields a virtual row object,
175
+ # which is easy to use to create identifiers and functions.
176
+ #
177
+ # If both a block and regular argument
155
178
  # are provided, they get ANDed together.
156
179
  #
157
180
  # Examples:
@@ -177,14 +200,8 @@ module Sequel
177
200
  #
178
201
  # See doc/dataset_filters.rdoc for more examples and details.
179
202
  def filter(*cond, &block)
180
- clause = (@opts[:having] ? :having : :where)
181
- cond = cond.first if cond.size == 1
182
- cond = transform_save(cond) if @transform if cond.is_a?(Hash)
183
- cond = filter_expr(cond, &block)
184
- cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause] && !@opts[clause].blank?
185
- clone(clause => cond)
203
+ _filter(@opts[:having] ? :having : :where, *cond, &block)
186
204
  end
187
- alias_method :where, :filter
188
205
 
189
206
  # The first source (primary table) for this dataset. If the dataset doesn't
190
207
  # have a table, raises an error. If the table is aliased, returns the aliased name.
@@ -205,6 +222,9 @@ module Sequel
205
222
  end
206
223
 
207
224
  # Returns a copy of the dataset with the source changed.
225
+ #
226
+ # dataset.from(:blah) # SQL: SELECT * FROM blah
227
+ # dataset.from(:blah, :foo) # SQL: SELECT * FROM blah, foo
208
228
  def from(*source)
209
229
  clone(:from => source)
210
230
  end
@@ -232,22 +252,30 @@ module Sequel
232
252
  # in some databases). See Sequel::SQL::StringExpression.like. Note that the
233
253
  # total number of pattern matches will be cols.length * terms.length,
234
254
  # which could cause performance issues.
255
+ #
256
+ # dataset.grep(:a, '%test%') # SQL: SELECT * FROM items WHERE a LIKE '%test%'
257
+ # dataset.grep([:a, :b], %w'%test% foo') # SQL: SELECT * FROM items WHERE a LIKE '%test%' OR a LIKE 'foo' OR b LIKE '%test%' OR b LIKE 'foo'
235
258
  def grep(cols, terms)
236
259
  filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
237
260
  end
238
261
 
239
262
  # Returns a copy of the dataset with the results grouped by the value of
240
- # the given columns
263
+ # the given columns.
264
+ #
265
+ # dataset.group(:id) # SELECT * FROM items GROUP BY id
266
+ # dataset.group(:id, :name) # SELECT * FROM items GROUP BY id, name
241
267
  def group(*columns)
242
268
  clone(:group => columns)
243
269
  end
244
- alias_method :group_by, :group
270
+ alias group_by group
245
271
 
246
- # Returns a copy of the dataset with the having conditions changed. Raises
247
- # an error if the dataset has not been grouped. See also #filter.
272
+ # Returns a copy of the dataset with the HAVING conditions changed. Raises
273
+ # an error if the dataset has not been grouped. See #filter for argument types.
274
+ #
275
+ # dataset.group(:sum).having(:sum=>10) # SQL: SELECT * FROM items GROUP BY sum HAVING sum = 10
248
276
  def having(*cond, &block)
249
- raise(Error::InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
250
- clone(:having=>{}).filter(*cond, &block)
277
+ raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
278
+ _filter(:having, *cond, &block)
251
279
  end
252
280
 
253
281
  # Inserts multiple values. If a block is given it is invoked for each
@@ -266,7 +294,7 @@ module Sequel
266
294
  # the resulting statement includes column names. If no values are given,
267
295
  # the resulting statement includes a DEFAULT VALUES clause.
268
296
  #
269
- # dataset.insert_sql() #=> 'INSERT INTO items DEFAULT VALUES'
297
+ # dataset.insert_sql #=> 'INSERT INTO items DEFAULT VALUES'
270
298
  # dataset.insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
271
299
  # dataset.insert_sql(:a => 1, :b => 2) #=>
272
300
  # 'INSERT INTO items (a, b) VALUES (1, 2)'
@@ -279,7 +307,7 @@ module Sequel
279
307
  values = {}
280
308
  when 1
281
309
  vals = values.at(0)
282
- if vals.is_one_of?(Hash, Dataset, Array)
310
+ if [Hash, Dataset, Array].any?{|c| vals.is_a?(c)}
283
311
  values = vals
284
312
  elsif vals.respond_to?(:values)
285
313
  values = vals.values
@@ -360,7 +388,7 @@ module Sequel
360
388
 
361
389
  # Returns a joined dataset. Uses the following arguments:
362
390
  #
363
- # * type - The type of join to do (:inner, :left_outer, :right_outer, :full)
391
+ # * type - The type of join to do (e.g. :inner)
364
392
  # * table - Depends on type:
365
393
  # * Dataset - a subselect is performed with an alias of tN for some value of N
366
394
  # * Model (or anything responding to :table_name) - table.table_name
@@ -389,7 +417,7 @@ module Sequel
389
417
  # the table alias/name for the last joined (or first table), and an array of previous
390
418
  # SQL::JoinClause.
391
419
  def join_table(type, table, expr=nil, options={}, &block)
392
- if options.is_one_of?(Symbol, String)
420
+ if [Symbol, String].any?{|c| options.is_a?(c)}
393
421
  table_alias = options
394
422
  last_alias = nil
395
423
  else
@@ -414,7 +442,7 @@ module Sequel
414
442
  SQL::JoinUsingClause.new(expr, type, table, table_alias)
415
443
  else
416
444
  last_alias ||= @opts[:last_joined_table] || (first_source.is_a?(Dataset) ? 't1' : first_source)
417
- if Hash === expr or (Array === expr and expr.all_two_pairs?)
445
+ if Sequel.condition_specifier?(expr)
418
446
  expr = expr.collect do |k, v|
419
447
  k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
420
448
  v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
@@ -436,12 +464,15 @@ module Sequel
436
464
  # If given an integer, the dataset will contain only the first l results.
437
465
  # If given a range, it will contain only those at offsets within that
438
466
  # range. If a second argument is given, it is used as an offset.
467
+ #
468
+ # dataset.limit(10) # SQL: SELECT * FROM items LIMIT 10
469
+ # dataset.limit(10, 20) # SQL: SELECT * FROM items LIMIT 10 OFFSET 20
439
470
  def limit(l, o = nil)
440
471
  return from_self.limit(l, o) if @opts[:sql]
441
472
 
442
473
  if Range === l
443
474
  o = l.first
444
- l = l.interval + 1
475
+ l = l.last - l.first + (l.exclude_end? ? 0 : 1)
445
476
  end
446
477
  l = l.to_i
447
478
  raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
@@ -509,28 +540,25 @@ module Sequel
509
540
  # This method should be overridden by descendants if the support
510
541
  # inserting multiple records in a single SQL statement.
511
542
  def multi_insert_sql(columns, values)
512
- table = quote_identifier(@opts[:from].first)
513
- columns = identifier_list(columns)
514
- values.map do |r|
515
- "INSERT INTO #{table} (#{columns}) VALUES #{literal(r)}"
516
- end
543
+ s = "INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES "
544
+ values.map{|r| s + literal(r)}
517
545
  end
518
546
 
519
547
  # Adds an alternate filter to an existing filter using OR. If no filter
520
548
  # exists an error is raised.
549
+ #
550
+ # dataset.filter(:a).or(:b) # SQL: SELECT * FROM items WHERE a OR b
521
551
  def or(*cond, &block)
522
552
  clause = (@opts[:having] ? :having : :where)
553
+ raise(InvalidOperation, "No existing filter found.") unless @opts[clause]
523
554
  cond = cond.first if cond.size == 1
524
- if @opts[clause]
525
- clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
526
- else
527
- raise Error::NoExistingFilter, "No existing filter found."
528
- end
555
+ clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
529
556
  end
530
557
 
531
558
  # Returns a copy of the dataset with the order changed. If a nil is given
532
559
  # the returned dataset has no order. This can accept multiple arguments
533
- # of varying kinds, and even SQL functions.
560
+ # of varying kinds, and even SQL functions. If a block is given, it is treated
561
+ # as a virtual row block, similar to filter.
534
562
  #
535
563
  # ds.order(:name).sql #=> 'SELECT * FROM items ORDER BY name'
536
564
  # ds.order(:a, :b).sql #=> 'SELECT * FROM items ORDER BY a, b'
@@ -538,19 +566,21 @@ module Sequel
538
566
  # ds.order(:a + :b).sql #=> 'SELECT * FROM items ORDER BY (a + b)'
539
567
  # ds.order(:name.desc).sql #=> 'SELECT * FROM items ORDER BY name DESC'
540
568
  # ds.order(:name.asc).sql #=> 'SELECT * FROM items ORDER BY name ASC'
541
- # ds.order(:arr|1).sql #=> 'SELECT * FROM items ORDER BY arr[1]'
569
+ # ds.order{|o| o.sum(:name)}.sql #=> 'SELECT * FROM items ORDER BY sum(name)'
542
570
  # ds.order(nil).sql #=> 'SELECT * FROM items'
543
- def order(*columns)
544
- columns += Array((yield SQL::VirtualRow.new)) if block_given?
571
+ def order(*columns, &block)
572
+ columns += Array(virtual_row_block_call(block)) if block
545
573
  clone(:order => (columns.compact.empty?) ? nil : columns)
546
574
  end
547
575
  alias_method :order_by, :order
548
576
 
549
577
  # Returns a copy of the dataset with the order columns added
550
578
  # to the existing order.
551
- def order_more(*columns)
552
- columns += Array((yield SQL::VirtualRow.new)) if block_given?
553
- order(*((@opts[:order] || []) + columns))
579
+ #
580
+ # ds.order(:a).order(:b).sql #=> 'SELECT * FROM items ORDER BY b'
581
+ # ds.order(:a).order_more(:b).sql #=> 'SELECT * FROM items ORDER BY a, b'
582
+ def order_more(*columns, &block)
583
+ order(*Array(@opts[:order]).concat(columns), &block)
554
584
  end
555
585
 
556
586
  # SQL fragment for the ordered expression, used in the ORDER BY
@@ -570,7 +600,7 @@ module Sequel
570
600
  # SQL fragment for the qualifed identifier, specifying
571
601
  # a table and a column (or schema and table).
572
602
  def qualified_identifier_sql(qcr)
573
- [qcr.table, qcr.column].map{|x| x.is_one_of?(SQL::QualifiedIdentifier, SQL::Identifier, Symbol) ? literal(x) : quote_identifier(x)}.join('.')
603
+ [qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
574
604
  end
575
605
 
576
606
  # Adds quoting to identifiers (columns and tables). If identifiers are not
@@ -582,7 +612,6 @@ module Sequel
582
612
  name = quoted_identifier(name) if quote_identifiers?
583
613
  name
584
614
  end
585
- alias_method :quote_column_ref, :quote_identifier
586
615
 
587
616
  # Separates the schema from the table and returns a string with them
588
617
  # quoted (if quoting identifiers)
@@ -603,7 +632,7 @@ module Sequel
603
632
  def reverse_order(*order)
604
633
  order(*invert_order(order.empty? ? @opts[:order] : order))
605
634
  end
606
- alias_method :reverse, :reverse_order
635
+ alias reverse reverse_order
607
636
 
608
637
  # Split the schema information from the table
609
638
  def schema_and_table(table_name)
@@ -624,27 +653,38 @@ module Sequel
624
653
  end
625
654
 
626
655
  # Returns a copy of the dataset with the columns selected changed
627
- # to the given columns.
628
- def select(*columns)
629
- columns += Array((yield SQL::VirtualRow.new)) if block_given?
656
+ # to the given columns. This also takes a virtual row block,
657
+ # similar to filter.
658
+ #
659
+ # dataset.select(:a) # SELECT a FROM items
660
+ # dataset.select(:a, :b) # SELECT a, b FROM items
661
+ # dataset.select{|o| o.a, o.sum(:b)} # SELECT a, sum(b) FROM items
662
+ def select(*columns, &block)
663
+ columns += Array(virtual_row_block_call(block)) if block
630
664
  clone(:select => columns)
631
665
  end
632
666
 
633
667
  # Returns a copy of the dataset selecting the wildcard.
668
+ #
669
+ # dataset.select(:a).select_all # SELECT * FROM items
634
670
  def select_all
635
671
  clone(:select => nil)
636
672
  end
637
673
 
638
674
  # Returns a copy of the dataset with the given columns added
639
675
  # to the existing selected columns.
640
- def select_more(*columns)
641
- columns += Array((yield SQL::VirtualRow.new)) if block_given?
642
- select(*((@opts[:select] || []) + columns))
676
+ #
677
+ # dataset.select(:a).select(:b) # SELECT b FROM items
678
+ # dataset.select(:a).select_more(:b) # SELECT a, b FROM items
679
+ def select_more(*columns, &block)
680
+ select(*Array(@opts[:select]).concat(columns), &block)
643
681
  end
644
682
 
645
- # Formats a SELECT statement using the given options and the dataset
646
- # options.
647
- def select_sql(opts = nil)
683
+ # Formats a SELECT statement
684
+ #
685
+ # dataset.select_sql # => "SELECT * FROM items"
686
+ def select_sql(opts = (defarg=true;nil))
687
+ Deprecation.deprecate("Calling Dataset#select_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).select_sql.") unless defarg
648
688
  opts = opts ? @opts.merge(opts) : @opts
649
689
  return static_sql(opts[:sql]) if opts[:sql]
650
690
  sql = 'SELECT'
@@ -653,8 +693,9 @@ module Sequel
653
693
  end
654
694
 
655
695
  # Same as select_sql, not aliased directly to make subclassing simpler.
656
- def sql(*args)
657
- select_sql(*args)
696
+ def sql(opts = (defarg=true;nil))
697
+ Deprecation.deprecate("Calling Dataset#select_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).select_sql.") unless defarg
698
+ defarg ? select_sql : select_sql(opts)
658
699
  end
659
700
 
660
701
  # SQL fragment for specifying subscripts (SQL arrays)
@@ -662,21 +703,9 @@ module Sequel
662
703
  "#{s.f}[#{s.sub.join(COMMA_SEPARATOR)}]"
663
704
  end
664
705
 
665
- # Converts a symbol into a column name. This method supports underscore
666
- # notation in order to express qualified (two underscores) and aliased
667
- # (three underscores) columns:
668
- #
669
- # ds = DB[:items]
670
- # :abc.to_column_ref(ds) #=> "abc"
671
- # :abc___a.to_column_ref(ds) #=> "abc AS a"
672
- # :items__abc.to_column_ref(ds) #=> "items.abc"
673
- # :items__abc___a.to_column_ref(ds) #=> "items.abc AS a"
674
- #
675
- def symbol_to_column_ref(sym)
676
- literal_symbol(sym)
677
- end
678
-
679
706
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
707
+ #
708
+ # dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items
680
709
  def unfiltered
681
710
  clone(:where => nil, :having => nil)
682
711
  end
@@ -690,13 +719,9 @@ module Sequel
690
719
  compound_clone(:union, dataset, all)
691
720
  end
692
721
 
693
- # Returns a copy of the dataset with the distinct option.
694
- def uniq(*args)
695
- clone(:distinct => args)
696
- end
697
- alias_method :distinct, :uniq
698
-
699
722
  # Returns a copy of the dataset with no order.
723
+ #
724
+ # dataset.order(:a).unordered # SELECT * FROM items
700
725
  def unordered
701
726
  order(nil)
702
727
  end
@@ -706,19 +731,18 @@ module Sequel
706
731
  # dataset.update_sql(:price => 100, :category => 'software') #=>
707
732
  # "UPDATE items SET price = 100, category = 'software'"
708
733
  #
709
- # Accepts a block, but such usage is discouraged.
710
- #
711
734
  # Raises an error if the dataset is grouped or includes more
712
735
  # than one table.
713
- def update_sql(values = {}, opts = nil)
736
+ def update_sql(values = {}, opts = (defarg=true;nil))
737
+ Deprecation.deprecate("Calling Dataset#update_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).update_sql.") unless defarg
714
738
  opts = opts ? @opts.merge(opts) : @opts
715
739
 
716
740
  return static_sql(opts[:sql]) if opts[:sql]
717
741
 
718
742
  if opts[:group]
719
- raise Error::InvalidOperation, "A grouped dataset cannot be updated"
743
+ raise InvalidOperation, "A grouped dataset cannot be updated"
720
744
  elsif (opts[:from].size > 1) or opts[:join]
721
- raise Error::InvalidOperation, "A joined dataset cannot be updated"
745
+ raise InvalidOperation, "A joined dataset cannot be updated"
722
746
  end
723
747
 
724
748
  sql = "UPDATE #{source_list(@opts[:from])} SET "
@@ -728,7 +752,7 @@ module Sequel
728
752
  # get values from hash
729
753
  values = transform_save(values) if @transform
730
754
  values.map do |k, v|
731
- "#{k.is_one_of?(String, Symbol) ? quote_identifier(k) : literal(k)} = #{literal(v)}"
755
+ "#{[String, Symbol].any?{|c| k.is_a?(c)} ? quote_identifier(k) : literal(k)} = #{literal(v)}"
732
756
  end.join(COMMA_SEPARATOR)
733
757
  else
734
758
  # copy values verbatim
@@ -742,16 +766,27 @@ module Sequel
742
766
  sql
743
767
  end
744
768
 
769
+ # Add a condition to the WHERE clause. See #filter for argument types.
770
+ #
771
+ # dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
772
+ # dataset.group(:a).having(:a).where(:b) # SELECT * FROM items WHERE b GROUP BY a HAVING a
773
+ def where(*cond, &block)
774
+ _filter(:where, *cond, &block)
775
+ end
776
+
745
777
  # Returns a copy of the dataset with the static SQL used. This is useful if you want
746
778
  # to keep the same row_proc/transform/graph, but change the SQL used to custom SQL.
747
- def with_sql(sql)
779
+ #
780
+ # dataset.with_sql('SELECT * FROM foo') # SELECT * FROM foo
781
+ def with_sql(sql, *args)
782
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
748
783
  clone(:sql=>sql)
749
784
  end
750
785
 
751
786
  [:inner, :full_outer, :right_outer, :left_outer].each do |jtype|
752
787
  class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end")
753
788
  end
754
- alias_method :join, :inner_join
789
+ alias join inner_join
755
790
 
756
791
  protected
757
792
 
@@ -764,6 +799,15 @@ module Sequel
764
799
 
765
800
  private
766
801
 
802
+ # Internal filter method so it works on either the having or where clauses.
803
+ def _filter(clause, *cond, &block)
804
+ cond = cond.first if cond.size == 1
805
+ cond = transform_save(cond) if @transform if cond.is_a?(Hash)
806
+ cond = filter_expr(cond, &block)
807
+ cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
808
+ clone(clause => cond)
809
+ end
810
+
767
811
  # SQL fragment for specifying an alias. expression should already be literalized.
768
812
  def as_sql(expression, aliaz)
769
813
  "#{expression} AS #{quote_identifier(aliaz)}"
@@ -772,7 +816,7 @@ module Sequel
772
816
  # Converts an array of column names into a comma seperated string of
773
817
  # column names. If the array is empty, a wildcard (*) is returned.
774
818
  def column_list(columns)
775
- if columns.blank?
819
+ if columns.nil? || columns.empty?
776
820
  WILDCARD
777
821
  else
778
822
  m = columns.map do |i|
@@ -811,7 +855,7 @@ module Sequel
811
855
  SQL::BooleanExpression.from_value_pairs(expr)
812
856
  end
813
857
  when Proc
814
- filter_expr(expr.call(SQL::VirtualRow.new))
858
+ filter_expr(virtual_row_block_call(expr))
815
859
  when SQL::NumericExpression, SQL::StringExpression
816
860
  raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
817
861
  when Symbol, SQL::Expression
@@ -862,7 +906,7 @@ module Sequel
862
906
 
863
907
  # SQL fragment for Array. Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
864
908
  def literal_array(v)
865
- v.all_two_pairs? ? literal_expression(v.sql_expr) : array_sql(v)
909
+ Sequel.condition_specifier?(v) ? literal_expression(SQL::BooleanExpression.from_value_pairs(v)) : array_sql(v)
866
910
  end
867
911
 
868
912
  # SQL fragment for BigDecimal
@@ -908,7 +952,7 @@ module Sequel
908
952
 
909
953
  # SQL fragment for Hash, treated as an expression
910
954
  def literal_hash(v)
911
- literal_expression(v.sql_expr)
955
+ literal_expression(SQL::BooleanExpression.from_value_pairs(v))
912
956
  end
913
957
 
914
958
  # SQL fragment for Integer
@@ -926,7 +970,14 @@ module Sequel
926
970
  "'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
927
971
  end
928
972
 
929
- # SQL fragment for Symbol, treated as an identifier, possibly aliased and/or qualified.
973
+ # Converts a symbol into a column name. This method supports underscore
974
+ # notation in order to express qualified (two underscores) and aliased
975
+ # (three underscores) columns:
976
+ #
977
+ # dataset.literal(:abc) #=> "abc"
978
+ # dataset.literal(:abc___a) #=> "abc AS a"
979
+ # dataset.literal(:items__abc) #=> "items.abc"
980
+ # dataset.literal(:items__abc___a) #=> "items.abc AS a"
930
981
  def literal_symbol(v)
931
982
  c_table, column, c_alias = split_symbol(v)
932
983
  qc = "#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}"
@@ -1080,12 +1131,12 @@ module Sequel
1080
1131
  # SQL fragment specifying a table name.
1081
1132
  def table_ref(t)
1082
1133
  case t
1134
+ when Symbol
1135
+ literal_symbol(t)
1083
1136
  when Dataset
1084
1137
  t.to_table_reference
1085
1138
  when Hash
1086
1139
  t.map{|k, v| as_sql(table_ref(k), v)}.join(COMMA_SEPARATOR)
1087
- when Symbol
1088
- symbol_to_column_ref(t)
1089
1140
  when String
1090
1141
  quote_identifier(t)
1091
1142
  else