sequel 4.36.0 → 4.37.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bb4a5a3794c2842c41482c01c74e82c2eed2f548
4
- data.tar.gz: b4e04bef12f7580b1fa3857a9a6d820ce5e977df
3
+ metadata.gz: e32a73f763f4abf6ea2b3e2833cd0d0a85356842
4
+ data.tar.gz: 9856a7e6a4c175f16c745339609b8b4d3d6b4d61
5
5
  SHA512:
6
- metadata.gz: b35ce656b26c851e5b5e2bf19c4ce5a40e83f6d79d939561a6e2d584b5210f91cbef788dbbb0a6c47154baa91f039eea6fb81053bdb03f016bf2a40a0da0f5eb
7
- data.tar.gz: 829169bda7ea5e43e8ef6d6b9726ba950adb033e6a4f992a3fb677cded90ca1ec842bb90632ba21aa15b4c6184db0fb45d3ce37b908e9d8fcd4f1591e13a7954
6
+ metadata.gz: 5286ffd2c900e56b4d07598dd6ecb4fcdf7bfd5694a56537ba4ca29a55f1801055e42d6ccd2b49c2aa655065f63380d8f068ef55602bb81c768947540788afa4
7
+ data.tar.gz: 842a025e1ecdcbe2990dbfd5f40ebcf2d9db9408d73e62e73ef4c36e0a56c27affa76a60d780845abb34d23e3108d6783fb3f206f7a13f304daad868d62b2667
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 4.37.0 (2016-08-01)
2
+
3
+ * Add support for regular expression matching on Oracle 10g+ using REGEXP_LIKE (johndcaldwell) (#1221)
4
+
5
+ * Recognize an additional disconnect error in the postgres adapter (jeremyevans)
6
+
7
+ * Make connection pool remove connections for disconnect errors not raised as DatabaseDisconnectError (jeremyevans)
8
+
9
+ * Support mysql2 0.4+ native prepared statements and bound variables (jeremyevans)
10
+
11
+ * Add Database#values for VALUES support on SQLite 3.8.3+ (jeremyevans)
12
+
13
+ * Support create_view :columns option on SQLite 3.9.0+ (jeremyevans)
14
+
15
+ * Make migration reverser handle alter_table add_constraint using a hash as the first argument (soupmatt) (#1215)
16
+
17
+ * Make ASTTransformer handle Sequel.extract (jeremyevans) (#1213)
18
+
1
19
  === 4.36.0 (2016-07-01)
2
20
 
3
21
  * Deprecate use of Bignum class as generic type, since the behavior will change in ruby 2.4 (jeremyevans)
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ VERS = lambda do
6
6
  require File.expand_path("../lib/sequel/version", __FILE__)
7
7
  Sequel.version
8
8
  end
9
- CLEAN.include ["**/.*.sw?", "sequel-*.gem", ".config", "rdoc", "coverage", "www/public/*.html", "www/public/rdoc*", '**/*.rbc']
9
+ CLEAN.include ["**/.*.sw?", "sequel-*.gem", ".config", "rdoc", "coverage", "www/public/*.html", "www/public/rdoc*", '**/*.rbc', "spec/bin-sequel-*"]
10
10
 
11
11
  # Gem Packaging and Release
12
12
 
@@ -7,8 +7,8 @@ the following adapters:
7
7
 
8
8
  * ibmdb (prepared statements only)
9
9
  * jdbc
10
- * mysql (prepared statements only)
11
- * mysql2 (prepared statements only)
10
+ * mysql (server prepared statements using literalized connection variables)
11
+ * mysql2 (full support on 0.4+, otherwise server prepared statements using literalized connection variables)
12
12
  * oracle (requires type specifiers for nil/NULL values)
13
13
  * postgres (when using the pg driver)
14
14
  * sqlite
@@ -56,7 +56,7 @@
56
56
  Example.first.lock!('FOR NO KEY UPDATE')
57
57
  #=> SELECT * FROM examples WHERE id = 1 FOR NO KEY UPDATE LIMIT 1
58
58
 
59
- * Sequel::Database#skip_locked has been added, which skips locked rows
59
+ * Sequel::Dataset#skip_locked has been added, which skips locked rows
60
60
  when returning query results. This is useful whenever you are
61
61
  implementing a queue or similar data structure. Currently, this is
62
62
  supported on PostgreSQL 9.5+, Oracle, and Microsoft SQL Server.
@@ -0,0 +1,50 @@
1
+ = New Features
2
+
3
+ * Database#values has been added on SQLite#3.8.3+, operating similarly
4
+ to the support on PostgreSQL:
5
+
6
+ DB.values([[1, 2], [3, 4]]).select_map([:column1, :column2])
7
+ # => [[1, 2], [3, 4]]
8
+
9
+ * Regular expressions in dataset filters are now supported on Oracle
10
+ 10g+:
11
+
12
+ DB[:t].where(:c=>/re/)
13
+ # SELECT * FROM "T" WHERE REGEXP_LIKE("C",'re')
14
+
15
+ = Other Improvements
16
+
17
+ * Sequel now supports the use of native prepared statements and bound
18
+ variables in the mysql2 adapter, when mysql2 0.4+ is used.
19
+ Previously, the mysql2 adapter supported database prepared
20
+ statements, but variables were always literalized. That is still
21
+ supported when mysql2 <0.4 is used.
22
+
23
+ * The connection pool now removes connections if it detects a
24
+ disconnect error that is not raised as a
25
+ Sequel::DatabaseDisconnectError. Such exceptions are reraised
26
+ without converted them to Sequel::DatabaseDisconnectError, but the
27
+ related connection is now removed from the pool.
28
+
29
+ * The reversible migration support now handles add_constraint with an
30
+ options hash as the first argument.
31
+
32
+ * ASTTransformer now handles Sequel.extract, allowing Dataset#qualify
33
+ and other uses of ASTTransformer to work with such values.
34
+
35
+ * The create_view :columns option is now suppported on SQLite 3.9.0+.
36
+
37
+ * An additional disconnect error is now recognized in the postgres
38
+ adapter.
39
+
40
+ * A frozen string literal issue has been fixed when multiple different
41
+ database connection approaches have failed in the jdbc adapter.
42
+
43
+ = Backwards Compatibility
44
+
45
+ * External database adapters need to make sure that
46
+ Database#database_error_classes returns a valid result if called
47
+ during Database#initialize. If you have an external adapter where
48
+ one of the error classes depends on an argument given when
49
+ connecting (such as the connection string), you may have to make
50
+ some changes.
@@ -519,6 +519,12 @@ This modifies the default value of a column:
519
519
  set_column_default :copies_sold, 0
520
520
  end
521
521
 
522
+ To remove a default value for a column, use +nil+ as the value:
523
+
524
+ alter_table(:albums) do
525
+ set_column_default :copies_sold, nil
526
+ end
527
+
522
528
  === +set_column_type+
523
529
 
524
530
  This modifies a column's type. Most databases will attempt to convert existing values in
@@ -224,7 +224,7 @@ module Sequel
224
224
  c
225
225
  rescue JavaSQL::SQLException, NativeException, StandardError => e2
226
226
  unless e2.message == e.message
227
- e2.message << "\n#{e.class.name}: #{e.message}"
227
+ e2.message = "#{e2.message}\n#{e.class.name}: #{e.message}"
228
228
  end
229
229
  raise e2
230
230
  end
@@ -53,7 +53,7 @@ module Sequel
53
53
  SHARED_ADAPTER_SETUP = {
54
54
  'postgres' => lambda do |db|
55
55
  db.instance_eval do
56
- @server_version = 90400
56
+ @server_version = 90500
57
57
  initialize_postgres_adapter
58
58
  end
59
59
  db.extend(Module.new do
@@ -68,6 +68,7 @@ module Sequel
68
68
  end,
69
69
  'oracle' => lambda do |db|
70
70
  db.instance_eval do
71
+ @server_version = 11000000
71
72
  @primary_key_sequences = {}
72
73
  end
73
74
  end,
@@ -83,7 +84,7 @@ module Sequel
83
84
  end,
84
85
  'sqlite' => lambda do |db|
85
86
  db.instance_eval do
86
- @sqlite_version = 30804
87
+ @sqlite_version = 30903
87
88
  end
88
89
  end
89
90
  }
@@ -7,7 +7,7 @@ rescue LoadError
7
7
  end
8
8
  raise(LoadError, "require 'mysql' did not define Mysql::CLIENT_MULTI_RESULTS!\n You are probably using the pure ruby mysql.rb driver,\n which Sequel does not support. You need to install\n the C based adapter, and make sure that the mysql.so\n file is loaded instead of the mysql.rb file.\n") unless defined?(Mysql::CLIENT_MULTI_RESULTS)
9
9
 
10
- Sequel.require %w'shared/mysql_prepared_statements', 'adapters'
10
+ Sequel.require %w'utils/mysql_mysql2 utils/mysql_prepared_statements', 'adapters'
11
11
 
12
12
  module Sequel
13
13
  # Module for holding all MySQL-related classes and modules for Sequel.
@@ -41,6 +41,7 @@ module Sequel
41
41
  # Database class for MySQL databases used with Sequel.
42
42
  class Database < Sequel::Database
43
43
  include Sequel::MySQL::DatabaseMethods
44
+ include Sequel::MySQL::MysqlMysql2::DatabaseMethods
44
45
  include Sequel::MySQL::PreparedStatements::DatabaseMethods
45
46
 
46
47
  # Regular expression used for getting accurate number of rows
@@ -287,6 +288,7 @@ module Sequel
287
288
  # Dataset class for MySQL datasets accessed via the native driver.
288
289
  class Dataset < Sequel::Dataset
289
290
  include Sequel::MySQL::DatasetMethods
291
+ include Sequel::MySQL::MysqlMysql2::DatasetMethods
290
292
  include Sequel::MySQL::PreparedStatements::DatasetMethods
291
293
 
292
294
  Database::DatasetClass = self
@@ -1,15 +1,23 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'mysql2'
4
- Sequel.require %w'shared/mysql_prepared_statements', 'adapters'
4
+ Sequel.require %w'utils/mysql_mysql2', 'adapters'
5
5
 
6
6
  module Sequel
7
7
  # Module for holding all Mysql2-related classes and modules for Sequel.
8
8
  module Mysql2
9
+ NativePreparedStatements = if ::Mysql2::VERSION >= '0.4'
10
+ true
11
+ else
12
+ Sequel.require %w'utils/mysql_prepared_statements', 'adapters'
13
+ false
14
+ end
15
+
9
16
  # Database class for MySQL databases used with Sequel.
10
17
  class Database < Sequel::Database
11
18
  include Sequel::MySQL::DatabaseMethods
12
- include Sequel::MySQL::PreparedStatements::DatabaseMethods
19
+ include Sequel::MySQL::MysqlMysql2::DatabaseMethods
20
+ include Sequel::MySQL::PreparedStatements::DatabaseMethods unless NativePreparedStatements
13
21
 
14
22
  set_adapter_scheme :mysql2
15
23
 
@@ -37,6 +45,10 @@ module Sequel
37
45
  opts[:encoding] ||= opts[:charset]
38
46
  conn = ::Mysql2::Client.new(opts)
39
47
  conn.query_options.merge!(:symbolize_keys=>true, :cache_rows=>false)
48
+
49
+ if NativePreparedStatements
50
+ @default_query_options ||= conn.query_options.dup
51
+ end
40
52
 
41
53
  sqls = mysql_connection_setting_sqls
42
54
 
@@ -71,13 +83,60 @@ module Sequel
71
83
 
72
84
  private
73
85
 
86
+ if NativePreparedStatements
87
+ # Use a native mysql2 prepared statement to implement prepared statements.
88
+ def execute_prepared_statement(ps_name, opts, &block)
89
+ args = opts[:arguments]
90
+ ps = prepared_statement(ps_name)
91
+ sql = ps.prepared_sql
92
+
93
+ synchronize(opts[:server]) do |conn|
94
+ stmt, ps_sql = conn.prepared_statements[ps_name]
95
+ unless ps_sql == sql
96
+ stmt.close if stmt
97
+ stmt = log_connection_yield(conn, "Preparing #{ps_name}: #{sql}"){conn.prepare(sql)}
98
+ conn.prepared_statements[ps_name] = [stmt, sql]
99
+ end
100
+
101
+ if ps.log_sql
102
+ opts = Hash[opts]
103
+ opts = opts[:log_sql] = " (#{sql})"
104
+ end
105
+
106
+ _execute(conn, stmt, opts, &block)
107
+ end
108
+ end
109
+ end
110
+
74
111
  # Execute the given SQL on the given connection. If the :type
75
112
  # option is :select, yield the result of the query, otherwise
76
113
  # yield the connection if a block is given.
77
114
  def _execute(conn, sql, opts)
78
115
  begin
79
116
  stream = opts[:stream]
80
- r = log_connection_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql, conn){conn.query(sql, :database_timezone => timezone, :application_timezone => Sequel.application_timezone, :stream=>stream)}
117
+ if NativePreparedStatements
118
+ if args = opts[:arguments]
119
+ args = args.map{|arg| bound_variable_value(arg)}
120
+ end
121
+
122
+ case sql
123
+ when ::Mysql2::Statement
124
+ stmt = sql
125
+ when Dataset
126
+ sql = sql.sql
127
+ close_stmt = true
128
+ stmt = conn.prepare(sql)
129
+ end
130
+ end
131
+
132
+ r = log_connection_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql, conn, args) do
133
+ if stmt
134
+ conn.query_options.merge!(:cache_rows=>true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone, :stream=>stream, :cast_booleans=>convert_tinyint_to_bool)
135
+ stmt.execute(*args)
136
+ else
137
+ conn.query(sql, :database_timezone => timezone, :application_timezone => Sequel.application_timezone, :stream=>stream)
138
+ end
139
+ end
81
140
  if opts[:type] == :select
82
141
  if r
83
142
  if stream
@@ -98,6 +157,11 @@ module Sequel
98
157
  end
99
158
  rescue ::Mysql2::Error => e
100
159
  raise_error(e)
160
+ ensure
161
+ if stmt
162
+ conn.query_options.replace(@default_query_options)
163
+ stmt.close if close_stmt
164
+ end
101
165
  end
102
166
  end
103
167
 
@@ -106,6 +170,22 @@ module Sequel
106
170
  self.convert_tinyint_to_bool = Sequel::MySQL.convert_tinyint_to_bool
107
171
  end
108
172
 
173
+ if NativePreparedStatements
174
+ # Handle bound variable arguments that Mysql2 does not handle natively.
175
+ def bound_variable_value(arg)
176
+ case arg
177
+ when true
178
+ 1
179
+ when false
180
+ 0
181
+ when DateTime, Time
182
+ literal(arg)[1...-1]
183
+ else
184
+ arg
185
+ end
186
+ end
187
+ end
188
+
109
189
  # MySQL connections use the query method to execute SQL without a result
110
190
  def connection_execute_method
111
191
  :query
@@ -146,11 +226,31 @@ module Sequel
146
226
  # Dataset class for MySQL datasets accessed via the native driver.
147
227
  class Dataset < Sequel::Dataset
148
228
  include Sequel::MySQL::DatasetMethods
149
- include Sequel::MySQL::PreparedStatements::DatasetMethods
229
+ include Sequel::MySQL::MysqlMysql2::DatasetMethods
230
+ include Sequel::MySQL::PreparedStatements::DatasetMethods unless NativePreparedStatements
150
231
  STREAMING_SUPPORTED = ::Mysql2::VERSION >= '0.3.12'
151
232
 
152
233
  Database::DatasetClass = self
153
234
 
235
+ if NativePreparedStatements
236
+ PreparedStatementMethods = prepared_statements_module(
237
+ "sql = self; opts = Hash[opts]; opts[:arguments] = bind_arguments",
238
+ Sequel::Dataset::UnnumberedArgumentMapper,
239
+ %w"execute execute_dui execute_insert")
240
+
241
+ # Create a named prepared statement that is stored in the
242
+ # database (and connection) for reuse.
243
+ def prepare(type, name=nil, *values)
244
+ ps = to_prepared_statement(type, values)
245
+ ps.extend(PreparedStatementMethods)
246
+ if name
247
+ ps.prepared_statement_name = name
248
+ db.set_prepared_statement(name, ps)
249
+ end
250
+ ps
251
+ end
252
+ end
253
+
154
254
  # Yield all rows matching this dataset.
155
255
  def fetch_rows(sql)
156
256
  execute(sql) do |r|
@@ -199,16 +299,7 @@ module Sequel
199
299
 
200
300
  # Handle correct quoting of strings using ::Mysql2::Client#escape.
201
301
  def literal_string_append(sql, v)
202
- s = begin
203
- db.synchronize(@opts[:server]) do |c|
204
- begin
205
- c.escape(v)
206
- rescue ::Mysql2::Error => e
207
- db.send(:raise_error, e)
208
- end
209
- end
210
- end
211
-
302
+ s = db.synchronize(@opts[:server]){|c| c.escape(v)}
212
303
  sql << APOS << s << APOS
213
304
  end
214
305
  end
@@ -125,6 +125,7 @@ module Sequel
125
125
  'could not receive data from server',
126
126
  'no connection to the server',
127
127
  'connection not open',
128
+ 'connection is closed',
128
129
  'terminating connection due to administrator command',
129
130
  'PQconsumeInput() '
130
131
  ]
@@ -560,6 +561,12 @@ module Sequel
560
561
  [PGError]
561
562
  end
562
563
 
564
+ def disconnect_error?(exception, opts)
565
+ super ||
566
+ Adapter::DISCONNECT_ERROR_CLASSES.any?{|klass| exception.is_a?(klass)} ||
567
+ exception.message =~ Adapter::DISCONNECT_ERROR_RE
568
+ end
569
+
563
570
  def database_exception_sqlstate(exception, opts)
564
571
  if exception.respond_to?(:result) && (result = exception.result)
565
572
  result.error_field(::PGresult::PG_DIAG_SQLSTATE)
@@ -841,12 +848,12 @@ module Sequel
841
848
 
842
849
  # Use the driver's escape_bytea
843
850
  def literal_blob_append(sql, v)
844
- sql << APOS << db.synchronize(@opts[:server]){|c| c.check_disconnect_errors{c.escape_bytea(v)}} << APOS
851
+ sql << APOS << db.synchronize(@opts[:server]){|c| c.escape_bytea(v)} << APOS
845
852
  end
846
853
 
847
854
  # Use the driver's escape_string
848
855
  def literal_string_append(sql, v)
849
- sql << APOS << db.synchronize(@opts[:server]){|c| c.check_disconnect_errors{c.escape_string(v)}} << APOS
856
+ sql << APOS << db.synchronize(@opts[:server]){|c| c.escape_string(v)} << APOS
850
857
  end
851
858
 
852
859
  # For each row in the result set, yield a hash with column name symbol
@@ -90,6 +90,23 @@ module Sequel
90
90
  count > 0
91
91
  end
92
92
 
93
+ # The version of the Oracle server, used for determining capability.
94
+ def server_version(server=nil)
95
+ return @server_version if @server_version
96
+ @server_version = synchronize(server) do |conn|
97
+ (conn.server_version rescue nil) if conn.respond_to?(:server_version)
98
+ end
99
+ unless @server_version
100
+ @server_version = if m = /(\d+)\.(\d+)\.?(\d+)?\.?(\d+)?/.match(fetch("select version from PRODUCT_COMPONENT_VERSION where lower(product) like 'oracle%'").single_value)
101
+ (m[1].to_i*1000000) + (m[2].to_i*10000) + (m[3].to_i*100) + m[4].to_i
102
+ else
103
+ 0
104
+ end
105
+ end
106
+ @server_version
107
+ end
108
+
109
+
93
110
  # Oracle supports deferrable constraints.
94
111
  def supports_deferrable_constraints?
95
112
  true
@@ -305,6 +322,19 @@ module Sequel
305
322
  s2 = complex_expression_arg_pairs(x, &BITAND_PROC)
306
323
  Sequel.lit(["(", " - ", ")"], s1, s2)
307
324
  end
325
+ when :~, :'!~', :'~*', :'!~*'
326
+ raise InvalidOperation, "Pattern matching via regular expressions is not supported in this Oracle version" unless supports_regexp?
327
+ if op == :'!~' || op == :'!~*'
328
+ sql << 'NOT '
329
+ end
330
+ sql << 'REGEXP_LIKE('
331
+ literal_append(sql, args.at(0))
332
+ sql << ','
333
+ literal_append(sql, args.at(1))
334
+ if op == :'~*' || op == :'!~*'
335
+ sql << ", 'i'"
336
+ end
337
+ sql << ')'
308
338
  when :%, :<<, :>>, :'B~'
309
339
  complex_expression_emulate_append(sql, op, args)
310
340
  else
@@ -452,6 +482,16 @@ module Sequel
452
482
  true
453
483
  end
454
484
 
485
+ # The version of the database server
486
+ def server_version
487
+ db.server_version(@opts[:server])
488
+ end
489
+
490
+ # Oracle supports pattern matching via regular expressions
491
+ def supports_regexp?
492
+ server_version >= 10010002
493
+ end
494
+
455
495
  private
456
496
 
457
497
  # Allow preparing prepared statements, since determining the prepared sql to use for
@@ -204,6 +204,14 @@ module Sequel
204
204
  pragma_set(:temp_store, value)
205
205
  end
206
206
 
207
+ # Creates a dataset that uses the VALUES clause:
208
+ #
209
+ # DB.values([[1, 2], [3, 4]])
210
+ # VALUES ((1, 2), (3, 4))
211
+ def values(v)
212
+ @default_dataset.clone(:values=>v)
213
+ end
214
+
207
215
  # Array of symbols specifying the view names in the current database.
208
216
  #
209
217
  # Options:
@@ -328,7 +336,7 @@ module Sequel
328
336
 
329
337
  # SQLite support creating temporary views.
330
338
  def create_view_prefix_sql(name, options)
331
- "CREATE #{'TEMPORARY 'if options[:temp]}VIEW #{quote_schema_table(name)}"
339
+ create_view_sql_append_columns("CREATE #{'TEMPORARY 'if options[:temp]}VIEW #{quote_schema_table(name)}", options[:columns])
332
340
  end
333
341
 
334
342
  DATABASE_ERROR_REGEXPS = {
@@ -517,9 +525,11 @@ module Sequel
517
525
  DATETIME_OPEN = "datetime(".freeze
518
526
  ONLY_OFFSET = " LIMIT -1 OFFSET ".freeze
519
527
  OR = " OR ".freeze
528
+ SELECT_VALUES = "VALUES ".freeze
520
529
 
521
530
  Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
522
531
  Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert conflict into columns values'], ["else", %w'insert conflict into columns values']])
532
+ Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'with values compounds'], ['else', %w'with select distinct columns from join where group having compounds order limit lock']])
523
533
  Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
524
534
 
525
535
  def cast_sql_append(sql, expr, type)
@@ -747,6 +757,12 @@ module Sequel
747
757
  literal_append(sql, @opts[:offset])
748
758
  end
749
759
 
760
+ # Support VALUES clause instead of the SELECT clause to return rows.
761
+ def select_values_sql(sql)
762
+ sql << SELECT_VALUES
763
+ expression_list_append(sql, opts[:values])
764
+ end
765
+
750
766
  # SQLite supports quoted function names.
751
767
  def supports_quoted_function_names?
752
768
  true
@@ -12,19 +12,16 @@ module Sequel
12
12
  # Contains procs keyed on sub adapter type that extend the
13
13
  # given database object so it supports the correct database type.
14
14
  DATABASE_SETUP = {:postgres=>proc do |db|
15
- Sequel.require 'adapters/swift/postgres'
16
15
  db.extend(Sequel::Swift::Postgres::DatabaseMethods)
17
16
  db.extend_datasets Sequel::Postgres::DatasetMethods
18
17
  db.swift_class = ::Swift::DB::Postgres
19
18
  end,
20
19
  :mysql=>proc do |db|
21
- Sequel.require 'adapters/swift/mysql'
22
20
  db.extend(Sequel::Swift::MySQL::DatabaseMethods)
23
21
  db.dataset_class = Sequel::Swift::MySQL::Dataset
24
22
  db.swift_class = ::Swift::DB::Mysql
25
23
  end,
26
24
  :sqlite=>proc do |db|
27
- Sequel.require 'adapters/swift/sqlite'
28
25
  db.extend(Sequel::Swift::SQLite::DatabaseMethods)
29
26
  db.dataset_class = Sequel::Swift::SQLite::Dataset
30
27
  db.swift_class = ::Swift::DB::Sqlite3
@@ -38,6 +35,11 @@ module Sequel
38
35
  # The Swift adapter class being used by this database. Connections
39
36
  # in this database's connection pool will be instances of this class.
40
37
  attr_accessor :swift_class
38
+
39
+ def initialize(opts=OPTS)
40
+ Sequel.require "adapters/swift/#{opts[:db_type]}" if %w'postgres mysql sqlite'.include?(opts[:db_type].to_s)
41
+ super
42
+ end
41
43
 
42
44
  # Create an instance of swift_class for the given options.
43
45
  def connect(server)
@@ -0,0 +1,82 @@
1
+ # frozen-string-literal: true
2
+
3
+ Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
4
+
5
+ module Sequel
6
+ module MySQL
7
+ # This module is used by the mysql and mysql2 adapters to support
8
+ # prepared statements and stored procedures.
9
+ module MysqlMysql2
10
+ module DatabaseMethods
11
+ disconnect_errors = <<-END.split("\n").map(&:strip)
12
+ Commands out of sync; you can't run this command now
13
+ Can't connect to local MySQL server through socket
14
+ MySQL server has gone away
15
+ Lost connection to MySQL server during query
16
+ This connection is still waiting for a result, try again once you have the result
17
+ closed MySQL connection
18
+ END
19
+ # Error messages for mysql and mysql2 that indicate the current connection should be disconnected
20
+ MYSQL_DATABASE_DISCONNECT_ERRORS = /\A#{Regexp.union(disconnect_errors)}/o
21
+
22
+ # Support stored procedures on MySQL
23
+ def call_sproc(name, opts=OPTS, &block)
24
+ args = opts[:args] || []
25
+ execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
26
+ end
27
+
28
+ # Executes the given SQL using an available connection, yielding the
29
+ # connection if the block is given.
30
+ def execute(sql, opts=OPTS, &block)
31
+ if opts[:sproc]
32
+ call_sproc(sql, opts, &block)
33
+ elsif sql.is_a?(Symbol)
34
+ execute_prepared_statement(sql, opts, &block)
35
+ else
36
+ synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def add_prepared_statements_cache(conn)
43
+ class << conn
44
+ attr_accessor :prepared_statements
45
+ end
46
+ conn.prepared_statements = {}
47
+ end
48
+
49
+ # Stupid MySQL doesn't use SQLState error codes correctly, mapping
50
+ # all constraint violations to 23000 even though it recognizes
51
+ # different types.
52
+ def database_specific_error_class(exception, opts)
53
+ case exception.errno
54
+ when 1048
55
+ NotNullConstraintViolation
56
+ when 1062
57
+ UniqueConstraintViolation
58
+ when 1451, 1452
59
+ ForeignKeyConstraintViolation
60
+ else
61
+ super
62
+ end
63
+ end
64
+ end
65
+
66
+ module DatasetMethods
67
+ include Sequel::Dataset::StoredProcedures
68
+
69
+ StoredProcedureMethods = Sequel::Dataset.send(:prepared_statements_module,
70
+ "sql = @sproc_name; opts = Hash[opts]; opts[:args] = @sproc_args; opts[:sproc] = true",
71
+ Sequel::Dataset::StoredProcedureMethods, %w'execute execute_dui')
72
+
73
+ private
74
+
75
+ # Extend the dataset with the MySQL stored procedure methods.
76
+ def prepare_extend_sproc(ds)
77
+ ds.extend(StoredProcedureMethods)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,67 +1,11 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
4
-
5
3
  module Sequel
6
4
  module MySQL
7
- # This module is used by the mysql and mysql2 adapters to support
8
- # prepared statements and stored procedures.
9
5
  module PreparedStatements
10
6
  module DatabaseMethods
11
- disconnect_errors = <<-END.split("\n").map(&:strip)
12
- Commands out of sync; you can't run this command now
13
- Can't connect to local MySQL server through socket
14
- MySQL server has gone away
15
- Lost connection to MySQL server during query
16
- This connection is still waiting for a result, try again once you have the result
17
- closed MySQL connection
18
- END
19
- # Error messages for mysql and mysql2 that indicate the current connection should be disconnected
20
- MYSQL_DATABASE_DISCONNECT_ERRORS = /\A#{Regexp.union(disconnect_errors)}/o
21
-
22
- # Support stored procedures on MySQL
23
- def call_sproc(name, opts=OPTS, &block)
24
- args = opts[:args] || []
25
- execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
26
- end
27
-
28
- # Executes the given SQL using an available connection, yielding the
29
- # connection if the block is given.
30
- def execute(sql, opts=OPTS, &block)
31
- if opts[:sproc]
32
- call_sproc(sql, opts, &block)
33
- elsif sql.is_a?(Symbol)
34
- execute_prepared_statement(sql, opts, &block)
35
- else
36
- synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
37
- end
38
- end
39
-
40
7
  private
41
8
 
42
- def add_prepared_statements_cache(conn)
43
- class << conn
44
- attr_accessor :prepared_statements
45
- end
46
- conn.prepared_statements = {}
47
- end
48
-
49
- # Stupid MySQL doesn't use SQLState error codes correctly, mapping
50
- # all constraint violations to 23000 even though it recognizes
51
- # different types.
52
- def database_specific_error_class(exception, opts)
53
- case exception.errno
54
- when 1048
55
- NotNullConstraintViolation
56
- when 1062
57
- UniqueConstraintViolation
58
- when 1451, 1452
59
- ForeignKeyConstraintViolation
60
- else
61
- super
62
- end
63
- end
64
-
65
9
  # Executes a prepared statement on an available connection. If the
66
10
  # prepared statement already exists for the connection and has the same
67
11
  # SQL, reuse it, otherwise, prepare the new statement. Because of the
@@ -86,8 +30,6 @@ module Sequel
86
30
  end
87
31
 
88
32
  module DatasetMethods
89
- include Sequel::Dataset::StoredProcedures
90
-
91
33
  # Methods to add to MySQL prepared statement calls without using a
92
34
  # real database prepared statement and bound variables.
93
35
  module CallableStatementMethods
@@ -112,10 +54,6 @@ module Sequel
112
54
  end
113
55
  end
114
56
 
115
- StoredProcedureMethods = Sequel::Dataset.send(:prepared_statements_module,
116
- "sql = @sproc_name; opts = Hash[opts]; opts[:args] = @sproc_args; opts[:sproc] = true",
117
- Sequel::Dataset::StoredProcedureMethods, %w'execute execute_dui')
118
-
119
57
  # MySQL is different in that it supports prepared statements but not bound
120
58
  # variables outside of prepared statements. The default implementation
121
59
  # breaks the use of subselects in prepared statements, so extend the
@@ -138,14 +76,6 @@ module Sequel
138
76
  end
139
77
  ps
140
78
  end
141
-
142
- private
143
-
144
- # Extend the dataset with the MySQL stored procedure methods.
145
- def prepare_extend_sproc(ds)
146
- ds.extend(StoredProcedureMethods)
147
- end
148
-
149
79
  end
150
80
  end
151
81
  end
@@ -26,8 +26,14 @@ module Sequel
26
26
  h = {}
27
27
  o.each{|k, val| h[v(k)] = v(val)}
28
28
  h
29
+ when SQL::NumericExpression
30
+ if o.op == :extract
31
+ o.class.new(o.op, o.args[0], v(o.args[1]))
32
+ else
33
+ o.class.new(o.op, *v(o.args))
34
+ end
29
35
  when SQL::ComplexExpression
30
- SQL::ComplexExpression.new(o.op, *v(o.args))
36
+ o.class.new(o.op, *v(o.args))
31
37
  when SQL::Identifier
32
38
  SQL::Identifier.new(v(o.value))
33
39
  when SQL::QualifiedIdentifier
@@ -84,6 +84,7 @@ class Sequel::ConnectionPool
84
84
  def initialize(db, opts=OPTS)
85
85
  @db = db
86
86
  @after_connect = opts[:after_connect]
87
+ @error_classes = db.send(:database_error_classes).dup.freeze
87
88
  end
88
89
 
89
90
  # Alias for +size+, not aliased directly for ease of subclass implementation
@@ -102,6 +103,11 @@ class Sequel::ConnectionPool
102
103
  def disconnect_connection(conn)
103
104
  db.disconnect_connection(conn)
104
105
  end
106
+
107
+ # Whether the given exception is a disconnect exception.
108
+ def disconnect_error?(exception)
109
+ exception.is_a?(Sequel::DatabaseDisconnectError) || db.send(:disconnect_error?, exception, OPTS)
110
+ end
105
111
 
106
112
  # Return a new connection by calling the connection proc with the given server name,
107
113
  # and checking for connection errors.
@@ -50,8 +50,8 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
50
50
  begin
51
51
  server = pick_server(server)
52
52
  yield(@conns[server] ||= make_new(server))
53
- rescue Sequel::DatabaseDisconnectError
54
- disconnect_server(server)
53
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
54
+ disconnect_server(server) if disconnect_error?(e)
55
55
  raise
56
56
  end
57
57
  end
@@ -126,8 +126,8 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
126
126
  begin
127
127
  conn = acquire(t, server)
128
128
  yield conn
129
- rescue Sequel::DatabaseDisconnectError
130
- sync{@connections_to_remove << conn} if conn
129
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
130
+ sync{@connections_to_remove << conn} if conn && disconnect_error?(e)
131
131
  raise
132
132
  ensure
133
133
  sync{release(t, conn, server)} if conn
@@ -20,8 +20,8 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
20
20
  def hold(server=nil)
21
21
  begin
22
22
  yield(@conn ||= make_new(DEFAULT_SERVER))
23
- rescue Sequel::DatabaseDisconnectError
24
- disconnect
23
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
24
+ disconnect if disconnect_error?(e)
25
25
  raise
26
26
  end
27
27
  end
@@ -105,11 +105,13 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
105
105
  begin
106
106
  conn = acquire(t)
107
107
  yield conn
108
- rescue Sequel::DatabaseDisconnectError
109
- oconn = conn
110
- conn = nil
111
- disconnect_connection(oconn) if oconn
112
- @allocated.delete(t)
108
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
109
+ if disconnect_error?(e)
110
+ oconn = conn
111
+ conn = nil
112
+ disconnect_connection(oconn) if oconn
113
+ @allocated.delete(t)
114
+ end
113
115
  raise
114
116
  ensure
115
117
  sync{release(t)} if conn
@@ -494,6 +494,10 @@ module Sequel
494
494
  #
495
495
  # set_column_default(:artist_name, 'a') # ALTER COLUMN artist_name SET DEFAULT 'a'
496
496
  #
497
+ # To remove an existing default value, use +nil+ as the value:
498
+ #
499
+ # set_column_default(:artist_name, nil) # ALTER COLUMN artist_name SET DEFAULT NULL
500
+ #
497
501
  # On MySQL, make sure to use a symbol for the name of the column, as otherwise you
498
502
  # can lose the type and NULL/NOT NULL setting for the column.
499
503
  def set_column_default(name, default)
@@ -245,7 +245,9 @@ module Sequel
245
245
  end
246
246
 
247
247
  def add_constraint(*args)
248
- @actions << [:drop_constraint, args.first]
248
+ name = args.first
249
+ name = name.is_a?(Hash) ? name[:name] : name
250
+ @actions << [:drop_constraint, name]
249
251
  end
250
252
 
251
253
  def add_foreign_key(key, table, *args)
@@ -5,7 +5,7 @@ module Sequel
5
5
  MAJOR = 4
6
6
  # The minor version of Sequel. Bumped for every non-patch level
7
7
  # release, generally around once a month.
8
- MINOR = 36
8
+ MINOR = 37
9
9
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
10
10
  # releases that fix regressions from previous versions.
11
11
  TINY = 0
@@ -190,6 +190,24 @@ describe "SQLite temporary views" do
190
190
  end
191
191
  end
192
192
 
193
+ describe "SQLite VALUES support" do
194
+ before do
195
+ @db = DB
196
+ end
197
+
198
+ it "should create a dataset using the VALUES clause via #values" do
199
+ @db.values([[1, 2], [3, 4]]).map([:column1, :column2]).must_equal [[1, 2], [3, 4]]
200
+ end
201
+
202
+ it "should support VALUES with unions" do
203
+ @db.values([[1]]).union(@db.values([[3]])).map(&:values).map(&:first).must_equal [1, 3]
204
+ end
205
+
206
+ it "should support VALUES in CTEs" do
207
+ @db[:a].cross_join(:b).with(:a, @db.values([[1, 2]]), :args=>[:c1, :c2]).with(:b, @db.values([[3, 4]]), :args=>[:c3, :c4]).map([:c1, :c2, :c3, :c4]).must_equal [[1, 2, 3, 4]]
208
+ end
209
+ end if DB.sqlite_version >= 30803
210
+
193
211
  describe "SQLite type conversion" do
194
212
  before do
195
213
  @db = DB
@@ -125,13 +125,13 @@ end
125
125
 
126
126
  describe "A connection pool handling connection errors" do
127
127
  it "#hold should raise a Sequel::DatabaseConnectionError if an exception is raised by the connection_proc" do
128
- cpool = Sequel::ConnectionPool.get_pool(CONNECTION_POOL_DEFAULTS){raise Interrupt}
128
+ cpool = Sequel::ConnectionPool.get_pool(mock_db.call{raise Interrupt}, CONNECTION_POOL_DEFAULTS)
129
129
  proc{cpool.hold{:block_return}}.must_raise(Sequel::DatabaseConnectionError)
130
130
  cpool.created_count.must_equal 0
131
131
  end
132
132
 
133
133
  it "#hold should raise a Sequel::DatabaseConnectionError if nil is returned by the connection_proc" do
134
- cpool = Sequel::ConnectionPool.get_pool(CONNECTION_POOL_DEFAULTS){nil}
134
+ cpool = Sequel::ConnectionPool.get_pool(mock_db.call{nil}, CONNECTION_POOL_DEFAULTS)
135
135
  proc{cpool.hold{:block_return}}.must_raise(Sequel::DatabaseConnectionError)
136
136
  cpool.created_count.must_equal 0
137
137
  end
@@ -905,6 +905,48 @@ describe "A single threaded pool with multiple servers" do
905
905
  end
906
906
 
907
907
  AllConnectionPoolClassesSpecs = shared_description do
908
+ it "should have pool correctly handle disconnect errors not raised as DatabaseDisconnectError" do
909
+ db = mock_db.call{Object.new}
910
+ def db.dec; @dec ||= Class.new(StandardError) end
911
+ def db.database_error_classes; super + [dec] end
912
+ def db.disconnect_error?(e, opts); e.message =~ /foo/ end
913
+ cp = @class.new(db, {})
914
+
915
+ conn = nil
916
+ cp.hold do |c|
917
+ conn = c
918
+ end
919
+
920
+ proc do
921
+ cp.hold do |c|
922
+ c.must_equal conn
923
+ raise db.dec, "bar"
924
+ end
925
+ end.must_raise db.dec
926
+
927
+ proc do
928
+ cp.hold do |c|
929
+ c.must_equal conn
930
+ raise StandardError
931
+ end
932
+ end.must_raise StandardError
933
+
934
+ cp.hold do |c|
935
+ c.must_equal conn
936
+ end
937
+
938
+ proc do
939
+ cp.hold do |c|
940
+ c.must_equal conn
941
+ raise db.dec, "foo"
942
+ end
943
+ end.must_raise db.dec
944
+
945
+ cp.hold do |c|
946
+ c.wont_equal conn
947
+ end
948
+ end
949
+
908
950
  it "should have pool_type return a symbol" do
909
951
  @class.new(mock_db.call{123}, {}).pool_type.must_be_kind_of(Symbol)
910
952
  end
@@ -3888,6 +3888,10 @@ describe "Sequel::Dataset#qualify" do
3888
3888
  @ds.select{sum(:a).order(:a)}.qualify.sql.must_equal 'SELECT sum(t.a ORDER BY t.a) FROM t'
3889
3889
  end
3890
3890
 
3891
+ it "should handle Sequel.extract" do
3892
+ @ds.select(Sequel.extract(:year, :d)).qualify.sql.must_equal 'SELECT extract(year FROM t.d) FROM t'
3893
+ end
3894
+
3891
3895
  it "should handle SQL::DelayedEvaluation" do
3892
3896
  t = :a
3893
3897
  ds = @ds.filter(Sequel.delay{t}).qualify
@@ -444,10 +444,11 @@ describe "Sequel Mock Adapter" do
444
444
  end
445
445
 
446
446
  it "should automatically set version for adapters nedding versions" do
447
- Sequel.mock(:host=>'postgres').server_version.must_equal 90400
448
- Sequel.mock(:host=>'mssql').server_version.must_equal 11000000
449
- Sequel.mock(:host=>'mysql').server_version.must_equal 50617
450
- Sequel.mock(:host=>'sqlite').sqlite_version.must_equal 30804
447
+ Sequel.mock(:host=>'postgres').server_version.must_be :>=, 90400
448
+ Sequel.mock(:host=>'mssql').server_version.must_be :>=, 11000000
449
+ Sequel.mock(:host=>'mysql').server_version.must_be :>=, 50617
450
+ Sequel.mock(:host=>'sqlite').sqlite_version.must_be :>=, 30804
451
+ Sequel.mock(:host=>'oracle').server_version.must_be :>=, 11000000
451
452
  end
452
453
 
453
454
  it "should stub out the primary_key method for postgres" do
@@ -129,6 +129,7 @@ describe "Reversible Migrations with Sequel.migration{change{}}" do
129
129
  alter_table(:b) do
130
130
  add_column :d, String
131
131
  add_constraint :blah, 'd IS NOT NULL'
132
+ add_constraint({:name=>:merp}, 'a > 1')
132
133
  add_foreign_key :e, :b
133
134
  add_foreign_key [:e], :b, :name=>'e_fk'
134
135
  add_foreign_key [:e, :a], :b
@@ -154,6 +155,7 @@ describe "Reversible Migrations with Sequel.migration{change{}}" do
154
155
  [:alter_table, [
155
156
  [:add_column, :d, String],
156
157
  [:add_constraint, :blah, "d IS NOT NULL"],
158
+ [:add_constraint, {:name=>:merp}, "a > 1"],
157
159
  [:add_foreign_key, :e, :b],
158
160
  [:add_foreign_key, [:e], :b, {:name=>"e_fk"}],
159
161
  [:add_foreign_key, [:e, :a], :b],
@@ -182,6 +184,7 @@ describe "Reversible Migrations with Sequel.migration{change{}}" do
182
184
  [:drop_foreign_key, [:e, :a]],
183
185
  [:drop_foreign_key, [:e], {:name=>"e_fk"}],
184
186
  [:drop_foreign_key, :e],
187
+ [:drop_constraint, :merp],
185
188
  [:drop_constraint, :blah],
186
189
  [:drop_column, :d]]
187
190
  ],
@@ -47,7 +47,7 @@ describe "pg_json extension" do
47
47
 
48
48
  it "should raise an error when attempting to parse invalid json" do
49
49
  proc{@m.parse_json('')}.must_raise(Sequel::InvalidValue)
50
- proc{@m.parse_json('1')}.must_raise(Sequel::InvalidValue)
50
+ proc{@m.parse_json('a')}.must_raise(Sequel::InvalidValue)
51
51
 
52
52
  begin
53
53
  Sequel.instance_eval do
@@ -354,7 +354,7 @@ describe "Database schema modifiers" do
354
354
  @ds.select_order_map(:number).must_equal [1, 2, 2, 3, 4]
355
355
  end if DB.supports_views_with_local_check_option?
356
356
 
357
- cspecify "should create views with explicit columns correctly", :sqlite do
357
+ cspecify "should create views with explicit columns correctly", [proc{|db| db.sqlite_version < 30900}, :sqlite] do
358
358
  @db.create_view(:items_view, @ds.where(:number=>1), :columns=>[:n])
359
359
  @db[:items_view].map(:n).must_equal [1]
360
360
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.36.0
4
+ version: 4.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-01 00:00:00.000000000 Z
11
+ date: 2016-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -237,6 +237,7 @@ extra_rdoc_files:
237
237
  - doc/release_notes/4.34.0.txt
238
238
  - doc/release_notes/4.35.0.txt
239
239
  - doc/release_notes/4.36.0.txt
240
+ - doc/release_notes/4.37.0.txt
240
241
  files:
241
242
  - CHANGELOG
242
243
  - MIT-LICENSE
@@ -362,6 +363,7 @@ files:
362
363
  - doc/release_notes/4.34.0.txt
363
364
  - doc/release_notes/4.35.0.txt
364
365
  - doc/release_notes/4.36.0.txt
366
+ - doc/release_notes/4.37.0.txt
365
367
  - doc/release_notes/4.4.0.txt
366
368
  - doc/release_notes/4.5.0.txt
367
369
  - doc/release_notes/4.6.0.txt
@@ -424,7 +426,6 @@ files:
424
426
  - lib/sequel/adapters/shared/informix.rb
425
427
  - lib/sequel/adapters/shared/mssql.rb
426
428
  - lib/sequel/adapters/shared/mysql.rb
427
- - lib/sequel/adapters/shared/mysql_prepared_statements.rb
428
429
  - lib/sequel/adapters/shared/oracle.rb
429
430
  - lib/sequel/adapters/shared/postgres.rb
430
431
  - lib/sequel/adapters/shared/progress.rb
@@ -439,6 +440,8 @@ files:
439
440
  - lib/sequel/adapters/tinytds.rb
440
441
  - lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb
441
442
  - lib/sequel/adapters/utils/emulate_offset_with_row_number.rb
443
+ - lib/sequel/adapters/utils/mysql_mysql2.rb
444
+ - lib/sequel/adapters/utils/mysql_prepared_statements.rb
442
445
  - lib/sequel/adapters/utils/pg_types.rb
443
446
  - lib/sequel/adapters/utils/replace.rb
444
447
  - lib/sequel/adapters/utils/split_alter_table.rb