sequel 3.10.0 → 3.11.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 (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
@@ -15,10 +15,12 @@ class MockDataset < Sequel::Dataset
15
15
 
16
16
  def update(*args)
17
17
  @db.execute update_sql(*args)
18
+ 1
18
19
  end
19
20
 
20
21
  def delete(*args)
21
22
  @db.execute delete_sql(*args)
23
+ 1
22
24
  end
23
25
 
24
26
  def fetch_rows(sql)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-04-02 00:00:00 -07:00
12
+ date: 2010-05-03 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -32,6 +32,9 @@ extra_rdoc_files:
32
32
  - doc/schema.rdoc
33
33
  - doc/sharding.rdoc
34
34
  - doc/virtual_rows.rdoc
35
+ - doc/dataset_basics.rdoc
36
+ - doc/association_basics.rdoc
37
+ - doc/querying.rdoc
35
38
  - doc/release_notes/1.0.txt
36
39
  - doc/release_notes/1.1.txt
37
40
  - doc/release_notes/1.3.txt
@@ -61,6 +64,7 @@ extra_rdoc_files:
61
64
  - doc/release_notes/3.8.0.txt
62
65
  - doc/release_notes/3.9.0.txt
63
66
  - doc/release_notes/3.10.0.txt
67
+ - doc/release_notes/3.11.0.txt
64
68
  files:
65
69
  - COPYING
66
70
  - CHANGELOG
@@ -102,9 +106,13 @@ files:
102
106
  - doc/release_notes/3.8.0.txt
103
107
  - doc/release_notes/3.9.0.txt
104
108
  - doc/release_notes/3.10.0.txt
109
+ - doc/release_notes/3.11.0.txt
105
110
  - doc/schema.rdoc
106
111
  - doc/sharding.rdoc
107
112
  - doc/virtual_rows.rdoc
113
+ - doc/dataset_basics.rdoc
114
+ - doc/association_basics.rdoc
115
+ - doc/querying.rdoc
108
116
  - spec/adapters/firebird_spec.rb
109
117
  - spec/adapters/informix_spec.rb
110
118
  - spec/adapters/mssql_spec.rb
@@ -162,6 +170,7 @@ files:
162
170
  - spec/extensions/validation_helpers_spec.rb
163
171
  - spec/extensions/composition_spec.rb
164
172
  - spec/extensions/rcte_tree_spec.rb
173
+ - spec/extensions/instance_filters_spec.rb
165
174
  - spec/integration/associations_test.rb
166
175
  - spec/integration/database_test.rb
167
176
  - spec/integration/dataset_test.rb
@@ -236,12 +245,13 @@ files:
236
245
  - lib/sequel/database/schema_sql.rb
237
246
  - lib/sequel/dataset.rb
238
247
  - lib/sequel/dataset/actions.rb
239
- - lib/sequel/dataset/convenience.rb
240
248
  - lib/sequel/dataset/features.rb
241
249
  - lib/sequel/dataset/graph.rb
250
+ - lib/sequel/dataset/misc.rb
242
251
  - lib/sequel/dataset/prepared_statements.rb
243
252
  - lib/sequel/dataset/query.rb
244
253
  - lib/sequel/dataset/sql.rb
254
+ - lib/sequel/dataset/mutation.rb
245
255
  - lib/sequel/exceptions.rb
246
256
  - lib/sequel/extensions/blank.rb
247
257
  - lib/sequel/extensions/inflector.rb
@@ -290,6 +300,7 @@ files:
290
300
  - lib/sequel/plugins/validation_helpers.rb
291
301
  - lib/sequel/plugins/composition.rb
292
302
  - lib/sequel/plugins/rcte_tree.rb
303
+ - lib/sequel/plugins/instance_filters.rb
293
304
  - lib/sequel/sql.rb
294
305
  - lib/sequel/timezones.rb
295
306
  - lib/sequel/version.rb
@@ -1,326 +0,0 @@
1
- module Sequel
2
- class Dataset
3
- COMMA_SEPARATOR = ', '.freeze
4
- COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
5
- ARRAY_ACCESS_ERROR_MSG = 'You cannot call Dataset#[] with an integer or with no arguments.'.freeze
6
- ARG_BLOCK_ERROR_MSG = 'Must use either an argument or a block, not both'.freeze
7
- IMPORT_ERROR_MSG = 'Using Sequel::Dataset#import an empty column array is not allowed'.freeze
8
-
9
- # Returns the first record matching the conditions. Examples:
10
- #
11
- # ds[:id=>1] => {:id=1}
12
- def [](*conditions)
13
- raise(Error, ARRAY_ACCESS_ERROR_MSG) if (conditions.length == 1 and conditions.first.is_a?(Integer)) or conditions.length == 0
14
- first(*conditions)
15
- end
16
-
17
- # Update all records matching the conditions
18
- # with the values specified. Examples:
19
- #
20
- # ds[:id=>1] = {:id=>2} # SQL: UPDATE ... SET id = 2 WHERE id = 1
21
- def []=(conditions, values)
22
- filter(conditions).update(values)
23
- end
24
-
25
- # Returns the average value for the given column.
26
- def avg(column)
27
- aggregate_dataset.get{avg(column)}
28
- end
29
-
30
- # Returns true if no records exist in the dataset, false otherwise
31
- def empty?
32
- get(1).nil?
33
- end
34
-
35
- # If a integer argument is
36
- # given, it is interpreted as a limit, and then returns all
37
- # matching records up to that limit. If no argument is passed,
38
- # it returns the first matching record. If any other type of
39
- # argument(s) is passed, it is given to filter and the
40
- # first matching record is returned. If a block is given, it is used
41
- # to filter the dataset before returning anything. Examples:
42
- #
43
- # ds.first => {:id=>7}
44
- # ds.first(2) => [{:id=>6}, {:id=>4}]
45
- # ds.order(:id).first(2) => [{:id=>1}, {:id=>2}]
46
- # ds.first(:id=>2) => {:id=>2}
47
- # ds.first("id = 3") => {:id=>3}
48
- # ds.first("id = ?", 4) => {:id=>4}
49
- # ds.first{|o| o.id > 2} => {:id=>5}
50
- # ds.order(:id).first{|o| o.id > 2} => {:id=>3}
51
- # ds.first{|o| o.id > 2} => {:id=>5}
52
- # ds.first("id > ?", 4){|o| o.id < 6} => {:id=>5}
53
- # ds.order(:id).first(2){|o| o.id < 2} => [{:id=>1}]
54
- def first(*args, &block)
55
- ds = block ? filter(&block) : self
56
-
57
- if args.empty?
58
- ds.single_record
59
- else
60
- args = (args.size == 1) ? args.first : args
61
- if Integer === args
62
- ds.limit(args).all
63
- else
64
- ds.filter(args).single_record
65
- end
66
- end
67
- end
68
-
69
- # Return the column value for the first matching record in the dataset.
70
- # Raises an error if both an argument and block is given.
71
- #
72
- # ds.get(:id)
73
- # ds.get{|o| o.sum(:id)}
74
- def get(column=nil, &block)
75
- if column
76
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
77
- select(column).single_value
78
- else
79
- select(&block).single_value
80
- end
81
- end
82
-
83
- # Returns a dataset grouped by the given column with count by group,
84
- # order by the count of records. Column aliases may be supplied, and will
85
- # be included in the select clause.
86
- #
87
- # Examples:
88
- #
89
- # ds.group_and_count(:name).all => [{:name=>'a', :count=>1}, ...]
90
- # ds.group_and_count(:first_name, :last_name).all => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
91
- # ds.group_and_count(:first_name___name).all => [{:name=>'a', :count=>1}, ...]
92
- def group_and_count(*columns)
93
- group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
94
- end
95
-
96
- # Inserts multiple records into the associated table. This method can be
97
- # to efficiently insert a large amounts of records into a table. Inserts
98
- # are automatically wrapped in a transaction.
99
- #
100
- # This method is called with a columns array and an array of value arrays:
101
- #
102
- # dataset.import([:x, :y], [[1, 2], [3, 4]])
103
- #
104
- # This method also accepts a dataset instead of an array of value arrays:
105
- #
106
- # dataset.import([:x, :y], other_dataset.select(:a___x, :b___y))
107
- #
108
- # The method also accepts a :slice or :commit_every option that specifies
109
- # the number of records to insert per transaction. This is useful especially
110
- # when inserting a large number of records, e.g.:
111
- #
112
- # # this will commit every 50 records
113
- # dataset.import([:x, :y], [[1, 2], [3, 4], ...], :slice => 50)
114
- def import(columns, values, opts={})
115
- return @db.transaction{insert(columns, values)} if values.is_a?(Dataset)
116
-
117
- return if values.empty?
118
- raise(Error, IMPORT_ERROR_MSG) if columns.empty?
119
-
120
- if slice_size = opts[:commit_every] || opts[:slice]
121
- offset = 0
122
- loop do
123
- @db.transaction(opts){multi_insert_sql(columns, values[offset, slice_size]).each{|st| execute_dui(st)}}
124
- offset += slice_size
125
- break if offset >= values.length
126
- end
127
- else
128
- statements = multi_insert_sql(columns, values)
129
- @db.transaction{statements.each{|st| execute_dui(st)}}
130
- end
131
- end
132
-
133
- # Returns the interval between minimum and maximum values for the given
134
- # column.
135
- def interval(column)
136
- aggregate_dataset.get{max(column) - min(column)}
137
- end
138
-
139
- # Reverses the order and then runs first. Note that this
140
- # will not necessarily give you the last record in the dataset,
141
- # unless you have an unambiguous order. If there is not
142
- # currently an order for this dataset, raises an Error.
143
- def last(*args, &block)
144
- raise(Error, 'No order specified') unless @opts[:order]
145
- reverse.first(*args, &block)
146
- end
147
-
148
- # Maps column values for each record in the dataset (if a column name is
149
- # given), or performs the stock mapping functionality of Enumerable.
150
- # Raises an error if both an argument and block are given. Examples:
151
- #
152
- # ds.map(:id) => [1, 2, 3, ...]
153
- # ds.map{|r| r[:id] * 2} => [2, 4, 6, ...]
154
- def map(column=nil, &block)
155
- if column
156
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
157
- super(){|r| r[column]}
158
- else
159
- super(&block)
160
- end
161
- end
162
-
163
- # Returns the maximum value for the given column.
164
- def max(column)
165
- aggregate_dataset.get{max(column)}
166
- end
167
-
168
- # Returns the minimum value for the given column.
169
- def min(column)
170
- aggregate_dataset.get{min(column)}
171
- end
172
-
173
- # This is a front end for import that allows you to submit an array of
174
- # hashes instead of arrays of columns and values:
175
- #
176
- # dataset.multi_insert([{:x => 1}, {:x => 2}])
177
- #
178
- # Be aware that all hashes should have the same keys if you use this calling method,
179
- # otherwise some columns could be missed or set to null instead of to default
180
- # values.
181
- #
182
- # You can also use the :slice or :commit_every option that import accepts.
183
- def multi_insert(hashes, opts={})
184
- return if hashes.empty?
185
- columns = hashes.first.keys
186
- import(columns, hashes.map{|h| columns.map{|c| h[c]}}, opts)
187
- end
188
-
189
- # Returns a Range object made from the minimum and maximum values for the
190
- # given column.
191
- def range(column)
192
- if r = aggregate_dataset.select{[min(column).as(v1), max(column).as(v2)]}.first
193
- (r[:v1]..r[:v2])
194
- end
195
- end
196
-
197
- # Returns a hash with key_column values as keys and value_column values as
198
- # values. Similar to to_hash, but only selects the two columns.
199
- def select_hash(key_column, value_column)
200
- select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
201
- end
202
-
203
- # Selects the column given (either as an argument or as a block), and
204
- # returns an array of all values of that column in the dataset. If you
205
- # give a block argument that returns an array with multiple entries,
206
- # the contents of the resulting array are undefined.
207
- def select_map(column=nil, &block)
208
- ds = naked.ungraphed
209
- ds = if column
210
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
211
- ds.select(column)
212
- else
213
- ds.select(&block)
214
- end
215
- ds.map{|r| r.values.first}
216
- end
217
-
218
- # The same as select_map, but in addition orders the array by the column.
219
- def select_order_map(column=nil, &block)
220
- ds = naked.ungraphed
221
- ds = if column
222
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
223
- ds.select(column).order(unaliased_identifier(column))
224
- else
225
- ds.select(&block).order(&block)
226
- end
227
- ds.map{|r| r.values.first}
228
- end
229
-
230
- # Returns the first record in the dataset.
231
- def single_record
232
- clone(:limit=>1).each{|r| return r}
233
- nil
234
- end
235
-
236
- # Returns the first value of the first record in the dataset.
237
- # Returns nil if dataset is empty.
238
- def single_value
239
- if r = naked.ungraphed.single_record
240
- r.values.first
241
- end
242
- end
243
-
244
- # Returns the sum for the given column.
245
- def sum(column)
246
- aggregate_dataset.get{sum(column)}
247
- end
248
-
249
- # Returns a string in CSV format containing the dataset records. By
250
- # default the CSV representation includes the column titles in the
251
- # first line. You can turn that off by passing false as the
252
- # include_column_titles argument.
253
- #
254
- # This does not use a CSV library or handle quoting of values in
255
- # any way. If any values in any of the rows could include commas or line
256
- # endings, you shouldn't use this.
257
- def to_csv(include_column_titles = true)
258
- n = naked
259
- cols = n.columns
260
- csv = ''
261
- csv << "#{cols.join(COMMA_SEPARATOR)}\r\n" if include_column_titles
262
- n.each{|r| csv << "#{cols.collect{|c| r[c]}.join(COMMA_SEPARATOR)}\r\n"}
263
- csv
264
- end
265
-
266
- # Returns a hash with one column used as key and another used as value.
267
- # If rows have duplicate values for the key column, the latter row(s)
268
- # will overwrite the value of the previous row(s). If the value_column
269
- # is not given or nil, uses the entire hash as the value.
270
- def to_hash(key_column, value_column = nil)
271
- inject({}) do |m, r|
272
- m[r[key_column]] = value_column ? r[value_column] : r
273
- m
274
- end
275
- end
276
-
277
- # Creates a unique table alias that hasn't already been used in the dataset.
278
- # table_alias can be any type of object accepted by alias_symbol.
279
- # The symbol returned will be the implicit alias in the argument,
280
- # possibly appended with "_N" if the implicit alias has already been
281
- # used, where N is an integer starting at 0 and increasing until an
282
- # unused one is found.
283
- def unused_table_alias(table_alias)
284
- table_alias = alias_symbol(table_alias)
285
- used_aliases = []
286
- used_aliases += opts[:from].map{|t| alias_symbol(t)} if opts[:from]
287
- used_aliases += opts[:join].map{|j| j.table_alias ? alias_alias_symbol(j.table_alias) : alias_symbol(j.table)} if opts[:join]
288
- if used_aliases.include?(table_alias)
289
- i = 0
290
- loop do
291
- ta = :"#{table_alias}_#{i}"
292
- return ta unless used_aliases.include?(ta)
293
- i += 1
294
- end
295
- else
296
- table_alias
297
- end
298
- end
299
-
300
- private
301
-
302
- # Return a plain symbol given a potentially qualified or aliased symbol,
303
- # specifying the symbol that is likely to be used as the hash key
304
- # for the column when records are returned.
305
- def hash_key_symbol(s)
306
- raise(Error, "#{s.inspect} is not a symbol") unless s.is_a?(Symbol)
307
- _, c, a = split_symbol(s)
308
- (a || c).to_sym
309
- end
310
-
311
- # Return the unaliased part of the identifier. Handles both
312
- # implicit aliases in symbols, as well as SQL::AliasedExpression
313
- # objects. Other objects are returned as is.
314
- def unaliased_identifier(c)
315
- case c
316
- when Symbol
317
- c_table, column, _ = split_symbol(c)
318
- c_table ? column.to_sym.qualify(c_table) : column.to_sym
319
- when SQL::AliasedExpression
320
- c.expression
321
- else
322
- c
323
- end
324
- end
325
- end
326
- end