sequel 3.1.0 → 3.2.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 (65) hide show
  1. data/CHANGELOG +76 -0
  2. data/Rakefile +2 -2
  3. data/bin/sequel +9 -4
  4. data/doc/opening_databases.rdoc +279 -0
  5. data/doc/release_notes/3.2.0.txt +268 -0
  6. data/doc/virtual_rows.rdoc +42 -51
  7. data/lib/sequel/adapters/ado.rb +2 -5
  8. data/lib/sequel/adapters/db2.rb +5 -0
  9. data/lib/sequel/adapters/do.rb +3 -0
  10. data/lib/sequel/adapters/firebird.rb +6 -4
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +10 -8
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -4
  14. data/lib/sequel/adapters/mysql.rb +6 -19
  15. data/lib/sequel/adapters/odbc.rb +14 -18
  16. data/lib/sequel/adapters/openbase.rb +8 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +14 -8
  18. data/lib/sequel/adapters/shared/mysql.rb +53 -28
  19. data/lib/sequel/adapters/shared/oracle.rb +21 -12
  20. data/lib/sequel/adapters/shared/postgres.rb +46 -26
  21. data/lib/sequel/adapters/shared/progress.rb +10 -5
  22. data/lib/sequel/adapters/shared/sqlite.rb +28 -12
  23. data/lib/sequel/adapters/sqlite.rb +4 -3
  24. data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
  25. data/lib/sequel/connection_pool.rb +4 -3
  26. data/lib/sequel/database.rb +110 -10
  27. data/lib/sequel/database/schema_sql.rb +12 -3
  28. data/lib/sequel/dataset.rb +40 -3
  29. data/lib/sequel/dataset/convenience.rb +0 -11
  30. data/lib/sequel/dataset/graph.rb +25 -11
  31. data/lib/sequel/dataset/sql.rb +176 -68
  32. data/lib/sequel/extensions/migration.rb +37 -21
  33. data/lib/sequel/extensions/schema_dumper.rb +8 -61
  34. data/lib/sequel/model.rb +3 -3
  35. data/lib/sequel/model/associations.rb +9 -1
  36. data/lib/sequel/model/base.rb +8 -1
  37. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  38. data/lib/sequel/sql.rb +125 -18
  39. data/lib/sequel/version.rb +1 -1
  40. data/spec/adapters/ado_spec.rb +1 -0
  41. data/spec/adapters/firebird_spec.rb +1 -0
  42. data/spec/adapters/informix_spec.rb +1 -0
  43. data/spec/adapters/mysql_spec.rb +23 -8
  44. data/spec/adapters/oracle_spec.rb +1 -0
  45. data/spec/adapters/postgres_spec.rb +52 -4
  46. data/spec/adapters/spec_helper.rb +2 -2
  47. data/spec/adapters/sqlite_spec.rb +2 -1
  48. data/spec/core/connection_pool_spec.rb +16 -0
  49. data/spec/core/database_spec.rb +174 -0
  50. data/spec/core/dataset_spec.rb +121 -26
  51. data/spec/core/expression_filters_spec.rb +156 -0
  52. data/spec/core/object_graph_spec.rb +20 -1
  53. data/spec/core/schema_spec.rb +5 -5
  54. data/spec/extensions/migration_spec.rb +140 -74
  55. data/spec/extensions/schema_dumper_spec.rb +3 -69
  56. data/spec/extensions/single_table_inheritance_spec.rb +6 -0
  57. data/spec/integration/dataset_test.rb +84 -2
  58. data/spec/integration/schema_test.rb +24 -5
  59. data/spec/integration/spec_helper.rb +8 -6
  60. data/spec/model/eager_loading_spec.rb +9 -0
  61. data/spec/model/record_spec.rb +35 -8
  62. metadata +8 -7
  63. data/lib/sequel/adapters/utils/date_format.rb +0 -21
  64. data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
  65. data/lib/sequel/adapters/utils/unsupported.rb +0 -50
@@ -1,65 +1,56 @@
1
1
  = Virtual Row Blocks
2
2
 
3
- Dataset methods filter, order, and select all take blocks that yield
4
- instances of Sequel::SQL::VirtualRow. These are referred to as
3
+ Dataset methods filter, order, and select all take blocks that either yield
4
+ instances of Sequel::SQL::VirtualRow (if the block takes an argument), or
5
+ are evaluated in the context of an instance of Sequel::SQL::VirtualRow. These are referred to as
5
6
  virtual row blocks. Many other dataset methods pass the blocks
6
7
  they are given into one of those three methods, so there are actually
7
8
  many Sequel methods that take virtual row blocks.
8
9
 
9
10
  VirtualRow is a class that returns SQL::Indentifiers,
10
- SQL::QualifiedIdentifiers, or SQL::Functions depending on how it is
11
+ SQL::QualifiedIdentifiers, SQL::Functions, or SQL::WindowFunctions depending on how it is
11
12
  called. This is best shown by example:
12
13
 
13
14
  ds = DB[:items]
14
- ds.filter{|o| o.column > 1} # column > 1
15
- ds.filter{|o| o.table__column > 1} # table.column > 1
16
- ds.filter{|o| o.function(1) > 1} # function(1) > 1
17
-
18
- Basically, the rules are:
19
-
20
- * If there are arguments, an SQL::Function is returned with the
21
- name of the method used and the arguments given.
22
-
23
- * If there are no arguments and the method contains a double
24
- underscore, split on the double underscore and return an
25
- SQL::QualifiedIdentifier with the table and column.
26
-
27
- * Otherwise, create an SQL::Identifier with the name of the
28
- method.
29
-
30
- One of the consequences of these rules is that you cannot
31
- create an SQL::Function that takes no arguments using a VirtualRow
32
- instance.
33
-
34
- In Sequel 2.12, the following is deprecated by default, as
35
- virtual row blocks are required to accept an argument:
36
-
37
- ds.filter{:column > 1} # column > 1
38
- ds.filter{:table__column > 1} # table.column > 1
39
- ds.filter{:function.sql_function(1) > 1} # function(1) > 1
40
-
41
- This is to keep backwards compatibility while notifying people
42
- to change their code. In Sequel 3.0, you will be able to
43
- do:
44
-
45
15
  ds.filter{column > 1} # column > 1
46
16
  ds.filter{table__column > 1} # table.column > 1
47
17
  ds.filter{function(1) > 1} # function(1) > 1
18
+ ds.select{version{}} # version()
19
+ ds.select{count(:*){}} # count(*)
20
+ ds.select{count(:distinct, col1){}} # count(DISTINCT col1)
21
+ ds.select{rank(:over){}} # rank() OVER ()
22
+ ds.select{count(:over, :*=>true){}} # count(*) OVER ()
23
+ ds.select{sum(:over, :args=>col1, :partition=>col2, :order=>col3){}} # sum(col1) OVER (PARTITION BY col2 ORDER BY col3)
24
+
25
+ Basically, the rules are:
48
26
 
49
- Sequel 3.0 will change the virtual row block support to use
50
- instance_eval if the block doesn't take an argument. This
51
- breaks backward compatibility because instance methods called
52
- inside the block will now have a virtual row instance as a
53
- receiver instead of the receiver where the block was defined.
54
- You can still use local variables in the enclosing scope
55
- inside the block, as they take on their usual meaning.
56
-
57
- If you find yourself needing to call instance methods for the
58
- current receiver inside a virtual row block, you should change
59
- the block to accept an argument, and use that argument whenever
60
- you need the virtual row support.
61
-
62
- You can get the Sequel 3.0 behavior in Sequel 2.12 using the
63
- following method:
64
-
65
- Sequel.virtual_row_instance_eval = true
27
+ * If a block is given:
28
+ * The block is currently not called. This may change in a future version.
29
+ * If there are no arguments, an SQL::Function with the name of
30
+ method used, and no arguments.
31
+ * If the first argument is :*, an SQL::Function is created with a single
32
+ wildcard argument (*).
33
+ * If the first argument is :distinct, an SQL::Function is created with
34
+ the keyword DISTINCT prefacing all remaining arguments.
35
+ * If the first argument is :over, the second argument if provided should
36
+ be a hash of options to pass to SQL::Window. The options hash can also
37
+ contain :*=>true to use a wildcard argument as the function argument, or
38
+ :args=>... to specify an array of arguments to use as the function arguments.
39
+ * If a block is not given:
40
+ * If there are arguments, an SQL::Function is returned with the
41
+ name of the method used and the arguments given.
42
+ * If there are no arguments and the method contains a double
43
+ underscore, split on the double underscore and return an
44
+ SQL::QualifiedIdentifier with the table and column.
45
+ * Otherwise, create an SQL::Identifier with the name of the
46
+ method.
47
+
48
+ If you use a virtual row block that doesn't take an argument,
49
+ the block is instance_evaled, so you can't reference methods
50
+ in the enclosing scope. If you need to call methods of the
51
+ enclosing scope, you should assign the results to local variables
52
+ before the block, or just make the block take an argument and use
53
+ that. If you want to create identifiers or qualified identifiers
54
+ with the same name as existing local variables, make sure ruby
55
+ knows it is a method call instead of a local variable reference
56
+ by not ommiting the parentheses at the end of the method call.
@@ -1,4 +1,3 @@
1
- Sequel.require 'adapters/utils/date_format'
2
1
  require 'win32ole'
3
2
 
4
3
  module Sequel
@@ -28,9 +27,9 @@ module Sequel
28
27
  # Connect to the database. In addition to the usual database options,
29
28
  # the following option has effect:
30
29
  #
31
- # * :command_timout - Sets the time in seconds to wait while attempting
30
+ # * :command_timeout - Sets the time in seconds to wait while attempting
32
31
  # to execute a command before cancelling the attempt and generating
33
- # an error. Specificially, it sets the ADO CommandTimeout property.
32
+ # an error. Specifically, it sets the ADO CommandTimeout property.
34
33
  # If this property is not set, the default of 30 seconds is used.
35
34
  # * :provider - Sets the Provider of this ADO connection (for example, "SQLOLEDB")
36
35
 
@@ -66,8 +65,6 @@ module Sequel
66
65
  end
67
66
 
68
67
  class Dataset < Sequel::Dataset
69
- include Dataset::SQLStandardDateFormat
70
-
71
68
  def fetch_rows(sql)
72
69
  execute(sql) do |s|
73
70
  @columns = s.Fields.extend(Enumerable).map do |column|
@@ -90,6 +90,11 @@ module Sequel
90
90
  self
91
91
  end
92
92
 
93
+ # DB2 supports window functions
94
+ def supports_window_functions?
95
+ true
96
+ end
97
+
93
98
  private
94
99
 
95
100
  def get_column_info(sth)
@@ -130,6 +130,7 @@ module Sequel
130
130
  # to do a transaction. So we close the connection created and
131
131
  # substitute our own.
132
132
  def begin_transaction(conn)
133
+ return super if supports_savepoints?
133
134
  log_info(TRANSACTION_BEGIN)
134
135
  t = ::DataObjects::Transaction.create_for_uri(uri)
135
136
  t.instance_variable_get(:@connection).close
@@ -141,6 +142,7 @@ module Sequel
141
142
  # DataObjects requires transactions be prepared before being
142
143
  # committed, so we do that.
143
144
  def commit_transaction(t)
145
+ return super if supports_savepoints?
144
146
  log_info(TRANSACTION_ROLLBACK)
145
147
  t.prepare
146
148
  t.commit
@@ -170,6 +172,7 @@ module Sequel
170
172
 
171
173
  # We use the transactions rollback method to rollback.
172
174
  def rollback_transaction(t)
175
+ return super if supports_savepoints?
173
176
  log_info(TRANSACTION_COMMIT)
174
177
  t.rollback
175
178
  end
@@ -1,5 +1,4 @@
1
1
  require 'fb'
2
- Sequel.require 'adapters/utils/unsupported'
3
2
 
4
3
  module Sequel
5
4
  # The Sequel Firebird adapter requires the ruby fb driver located at
@@ -199,14 +198,12 @@ module Sequel
199
198
 
200
199
  # Dataset class for Firebird datasets
201
200
  class Dataset < Sequel::Dataset
202
- include UnsupportedIntersectExcept
203
-
204
201
  BOOL_TRUE = '1'.freeze
205
202
  BOOL_FALSE = '0'.freeze
206
203
  NULL = LiteralString.new('NULL').freeze
207
204
  COMMA_SEPARATOR = ', '.freeze
208
205
  FIREBIRD_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
209
- SELECT_CLAUSE_ORDER = %w'distinct limit columns from join where group having compounds order'.freeze
206
+ SELECT_CLAUSE_ORDER = %w'with distinct limit columns from join where group having compounds order'.freeze
210
207
 
211
208
  # Yield all rows returned by executing the given SQL and converting
212
209
  # the types.
@@ -262,6 +259,11 @@ module Sequel
262
259
  sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
263
260
  end
264
261
 
262
+ # Firebird does not support INTERSECT or EXCEPT
263
+ def supports_intersect_except?
264
+ false
265
+ end
266
+
265
267
  private
266
268
 
267
269
  def hash_row(stmt, row)
@@ -1,4 +1,3 @@
1
- Sequel.require 'adapters/utils/unsupported'
2
1
  require 'informix'
3
2
 
4
3
  module Sequel
@@ -38,8 +37,6 @@ module Sequel
38
37
  end
39
38
 
40
39
  class Dataset < Sequel::Dataset
41
- include UnsupportedIntersectExcept
42
-
43
40
  SELECT_CLAUSE_ORDER = %w'limit distinct columns from join where having group compounds order'.freeze
44
41
 
45
42
  def fetch_rows(sql, &block)
@@ -64,6 +61,11 @@ module Sequel
64
61
 
65
62
  private
66
63
 
64
+ # Informix does not support INTERSECT or EXCEPT
65
+ def supports_intersect_except?
66
+ false
67
+ end
68
+
67
69
  def select_clause_order
68
70
  SELECT_CLAUSE_ORDER
69
71
  end
@@ -134,7 +134,9 @@ module Sequel
134
134
 
135
135
  # Connect to the database using JavaSQL::DriverManager.getConnection.
136
136
  def connect(server)
137
- setup_connection(JavaSQL::DriverManager.getConnection(uri(server_opts(server))))
137
+ args = [uri(server_opts(server))]
138
+ args.concat([opts[:user], opts[:password]]) if opts[:user] && opts[:password]
139
+ setup_connection(JavaSQL::DriverManager.getConnection(*args))
138
140
  end
139
141
 
140
142
  # Return instances of JDBC::Dataset with the given opts.
@@ -223,7 +225,7 @@ module Sequel
223
225
 
224
226
  # JDBC uses a statement object to execute SQL on the database
225
227
  def begin_transaction(conn)
226
- conn = conn.createStatement
228
+ conn = conn.createStatement unless supports_savepoints?
227
229
  super
228
230
  end
229
231
 
@@ -304,7 +306,7 @@ module Sequel
304
306
 
305
307
  # Close the given statement when removing the transaction
306
308
  def remove_transaction(stmt)
307
- stmt.close if stmt
309
+ stmt.close if stmt && !supports_savepoints?
308
310
  super
309
311
  end
310
312
 
@@ -464,14 +466,14 @@ module Sequel
464
466
  def process_result_set(result)
465
467
  # get column names
466
468
  meta = result.getMetaData
467
- column_count = meta.getColumnCount
468
- @columns = []
469
- column_count.times{|i| @columns << output_identifier(meta.getColumnLabel(i+1))}
470
-
469
+ cols = []
470
+ i = 0
471
+ meta.getColumnCount.times{cols << [output_identifier(meta.getColumnLabel(i+=1)), i]}
472
+ @columns = cols.map{|c| c.at(0)}
471
473
  # get rows
472
474
  while result.next
473
475
  row = {}
474
- @columns.each_with_index{|v, i| row[v] = convert_type(result.getObject(i+1))}
476
+ cols.each{|n, i| row[n] = convert_type(result.getObject(i))}
475
477
  yield row
476
478
  end
477
479
  end
@@ -1,5 +1,3 @@
1
- Sequel.require %w'date_format unsupported', 'adapters/utils'
2
-
3
1
  module Sequel
4
2
  module JDBC
5
3
  # Database and Dataset support for H2 databases accessed via JDBC.
@@ -65,8 +63,23 @@ module Sequel
65
63
 
66
64
  # Dataset class for H2 datasets accessed via JDBC.
67
65
  class Dataset < JDBC::Dataset
68
- include Dataset::SQLStandardDateFormat
69
- include Dataset::UnsupportedIsTrue
66
+ SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
67
+
68
+ # H2 requires SQL standard datetimes
69
+ def requires_sql_standard_datetimes?
70
+ true
71
+ end
72
+
73
+ # H2 doesn't support IS TRUE
74
+ def supports_is_true?
75
+ false
76
+ end
77
+
78
+ private
79
+
80
+ def select_clause_order
81
+ SELECT_CLAUSE_ORDER
82
+ end
70
83
  end
71
84
  end
72
85
  end
@@ -16,7 +16,8 @@ module Sequel
16
16
  # Use only a single proc for each type to save on memory
17
17
  MYSQL_TYPE_PROCS = {
18
18
  [0, 246] => lambda{|v| BigDecimal.new(v)}, # decimal
19
- [1, 2, 3, 8, 9, 13, 247, 248] => lambda{|v| v.to_i}, # integer
19
+ [1] => lambda{|v| Sequel.convert_tinyint_to_bool ? v.to_i != 0 : v.to_i}, # tinyint
20
+ [2, 3, 8, 9, 13, 247, 248] => lambda{|v| v.to_i}, # integer
20
21
  [4, 5] => lambda{|v| v.to_f}, # float
21
22
  [10, 14] => lambda{|v| convert_date_time(:string_to_date, v)}, # date
22
23
  [7, 12] => lambda{|v| convert_date_time(:string_to_datetime, v)}, # datetime
@@ -295,11 +296,12 @@ module Sequel
295
296
  # Yield all rows matching this dataset
296
297
  def fetch_rows(sql)
297
298
  execute(sql) do |r|
298
- column_types = []
299
- @columns = r.fetch_fields.map{|f| column_types << f.type; output_identifier(f.name)}
299
+ i = -1
300
+ cols = r.fetch_fields.map{|f| [output_identifier(f.name), MYSQL_TYPES[f.type], i+=1]}
301
+ @columns = cols.map{|c| c.first}
300
302
  while row = r.fetch_row
301
303
  h = {}
302
- @columns.each_with_index {|f, i| h[f] = convert_type(row[i], column_types[i])}
304
+ cols.each{|n, p, i| v = row[i]; h[n] = (v && p) ? p.call(v) : v}
303
305
  yield h
304
306
  end
305
307
  end
@@ -335,21 +337,6 @@ module Sequel
335
337
 
336
338
  private
337
339
 
338
- # Convert the type of v using the method in MYSQL_TYPES[type].
339
- def convert_type(v, type)
340
- if v
341
- if type == 1 && Sequel.convert_tinyint_to_bool
342
- # We special case tinyint here to avoid adding
343
- # a method to an ancestor of Fixnum
344
- v.to_i == 0 ? false : true
345
- else
346
- (b = MYSQL_TYPES[type]) ? b.call(v) : v
347
- end
348
- else
349
- nil
350
- end
351
- end
352
-
353
340
  # Set the :type option to :select if it hasn't been set.
354
341
  def execute(sql, opts={}, &block)
355
342
  super(sql, {:type=>:select}.merge(opts), &block)
@@ -44,16 +44,16 @@ module Sequel
44
44
  ODBC::Dataset.new(self, opts)
45
45
  end
46
46
 
47
- # ODBC returns native statement objects, which must be dropped if
48
- # you call execute manually, or you will get warnings. See the
49
- # fetch_rows method source code for an example of how to drop
50
- # the statements.
51
47
  def execute(sql, opts={})
52
48
  log_info(sql)
53
49
  synchronize(opts[:server]) do |conn|
54
- r = conn.run(sql)
55
- yield(r) if block_given?
56
- r
50
+ begin
51
+ r = conn.run(sql)
52
+ yield(r) if block_given?
53
+ ensure
54
+ r.drop if r
55
+ end
56
+ nil
57
57
  end
58
58
  end
59
59
 
@@ -85,19 +85,15 @@ module Sequel
85
85
 
86
86
  def fetch_rows(sql, &block)
87
87
  execute(sql) do |s|
88
- begin
89
- untitled_count = 0
90
- @columns = s.columns(true).map do |c|
91
- if (n = c.name).empty?
92
- n = UNTITLED_COLUMN % (untitled_count += 1)
93
- end
94
- output_identifier(n)
88
+ untitled_count = 0
89
+ @columns = s.columns(true).map do |c|
90
+ if (n = c.name).empty?
91
+ n = UNTITLED_COLUMN % (untitled_count += 1)
95
92
  end
96
- rows = s.fetch_all
97
- rows.each {|row| yield hash_row(row)} if rows
98
- ensure
99
- s.drop unless s.nil? rescue nil
93
+ output_identifier(n)
100
94
  end
95
+ rows = s.fetch_all
96
+ rows.each {|row| yield hash_row(row)} if rows
101
97
  end
102
98
  self
103
99
  end
@@ -37,6 +37,8 @@ module Sequel
37
37
  end
38
38
 
39
39
  class Dataset < Sequel::Dataset
40
+ SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
41
+
40
42
  def fetch_rows(sql)
41
43
  execute(sql) do |result|
42
44
  begin
@@ -52,6 +54,12 @@ module Sequel
52
54
  end
53
55
  self
54
56
  end
57
+
58
+ private
59
+
60
+ def select_clause_order
61
+ SELECT_CLAUSE_ORDER
62
+ end
55
63
  end
56
64
  end
57
65
  end
@@ -1,5 +1,3 @@
1
- Sequel.require 'adapters/utils/unsupported'
2
-
3
1
  module Sequel
4
2
  module MSSQL
5
3
  module DatabaseMethods
@@ -48,9 +46,7 @@ module Sequel
48
46
  end
49
47
 
50
48
  module DatasetMethods
51
- include Dataset::UnsupportedIntersectExcept
52
-
53
- SELECT_CLAUSE_ORDER = %w'limit distinct columns from with join where group order having compounds'.freeze
49
+ SELECT_CLAUSE_ORDER = %w'with limit distinct columns from table_options join where group order having compounds'.freeze
54
50
 
55
51
  def complex_expression_sql(op, args)
56
52
  case op
@@ -72,13 +68,23 @@ module Sequel
72
68
 
73
69
  # Allows you to do .nolock on a query
74
70
  def nolock
75
- clone(:with => "(NOLOCK)")
71
+ clone(:table_options => "(NOLOCK)")
76
72
  end
77
73
 
78
74
  def quoted_identifier(name)
79
75
  "[#{name}]"
80
76
  end
81
77
 
78
+ # Microsoft SQL Server does not support INTERSECT or EXCEPT
79
+ def supports_intersect_except?
80
+ false
81
+ end
82
+
83
+ # MSSQL 2005+ supports window functions
84
+ def supports_window_functions?
85
+ true
86
+ end
87
+
82
88
  private
83
89
 
84
90
  def literal_string(v)
@@ -96,8 +102,8 @@ module Sequel
96
102
  end
97
103
 
98
104
  # MSSQL uses the WITH statement to lock tables
99
- def select_with_sql(sql)
100
- sql << " WITH #{@opts[:with]}" if @opts[:with]
105
+ def select_table_options_sql(sql)
106
+ sql << " WITH #{@opts[:table_options]}" if @opts[:table_options]
101
107
  end
102
108
  end
103
109
  end