sequel 4.36.0 → 4.37.0

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