sequel 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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