sequel 3.10.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. data/CHANGELOG +68 -0
  2. data/COPYING +1 -1
  3. data/README.rdoc +87 -27
  4. data/bin/sequel +2 -4
  5. data/doc/association_basics.rdoc +1383 -0
  6. data/doc/dataset_basics.rdoc +106 -0
  7. data/doc/opening_databases.rdoc +45 -16
  8. data/doc/querying.rdoc +210 -0
  9. data/doc/release_notes/3.11.0.txt +254 -0
  10. data/doc/virtual_rows.rdoc +217 -31
  11. data/lib/sequel/adapters/ado.rb +28 -12
  12. data/lib/sequel/adapters/ado/mssql.rb +33 -1
  13. data/lib/sequel/adapters/amalgalite.rb +13 -8
  14. data/lib/sequel/adapters/db2.rb +1 -2
  15. data/lib/sequel/adapters/dbi.rb +7 -4
  16. data/lib/sequel/adapters/do.rb +14 -15
  17. data/lib/sequel/adapters/do/postgres.rb +4 -5
  18. data/lib/sequel/adapters/do/sqlite.rb +9 -0
  19. data/lib/sequel/adapters/firebird.rb +5 -10
  20. data/lib/sequel/adapters/informix.rb +2 -4
  21. data/lib/sequel/adapters/jdbc.rb +111 -49
  22. data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
  24. data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
  27. data/lib/sequel/adapters/mysql.rb +14 -5
  28. data/lib/sequel/adapters/odbc.rb +2 -4
  29. data/lib/sequel/adapters/odbc/mssql.rb +2 -4
  30. data/lib/sequel/adapters/openbase.rb +1 -2
  31. data/lib/sequel/adapters/oracle.rb +4 -8
  32. data/lib/sequel/adapters/postgres.rb +4 -11
  33. data/lib/sequel/adapters/shared/mssql.rb +22 -9
  34. data/lib/sequel/adapters/shared/mysql.rb +33 -30
  35. data/lib/sequel/adapters/shared/oracle.rb +0 -5
  36. data/lib/sequel/adapters/shared/postgres.rb +13 -11
  37. data/lib/sequel/adapters/shared/sqlite.rb +56 -10
  38. data/lib/sequel/adapters/sqlite.rb +16 -9
  39. data/lib/sequel/connection_pool.rb +6 -1
  40. data/lib/sequel/connection_pool/single.rb +1 -0
  41. data/lib/sequel/core.rb +6 -1
  42. data/lib/sequel/database.rb +52 -23
  43. data/lib/sequel/database/schema_generator.rb +6 -0
  44. data/lib/sequel/database/schema_methods.rb +5 -5
  45. data/lib/sequel/database/schema_sql.rb +1 -1
  46. data/lib/sequel/dataset.rb +4 -190
  47. data/lib/sequel/dataset/actions.rb +323 -1
  48. data/lib/sequel/dataset/features.rb +18 -2
  49. data/lib/sequel/dataset/graph.rb +7 -0
  50. data/lib/sequel/dataset/misc.rb +119 -0
  51. data/lib/sequel/dataset/mutation.rb +64 -0
  52. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  53. data/lib/sequel/dataset/query.rb +272 -6
  54. data/lib/sequel/dataset/sql.rb +186 -394
  55. data/lib/sequel/model.rb +4 -2
  56. data/lib/sequel/model/associations.rb +31 -14
  57. data/lib/sequel/model/base.rb +32 -13
  58. data/lib/sequel/model/exceptions.rb +8 -4
  59. data/lib/sequel/model/plugins.rb +3 -13
  60. data/lib/sequel/plugins/active_model.rb +26 -7
  61. data/lib/sequel/plugins/instance_filters.rb +98 -0
  62. data/lib/sequel/plugins/many_through_many.rb +1 -1
  63. data/lib/sequel/plugins/optimistic_locking.rb +25 -9
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +26 -0
  66. data/spec/adapters/mysql_spec.rb +33 -4
  67. data/spec/adapters/postgres_spec.rb +24 -1
  68. data/spec/adapters/spec_helper.rb +6 -0
  69. data/spec/adapters/sqlite_spec.rb +28 -0
  70. data/spec/core/connection_pool_spec.rb +17 -5
  71. data/spec/core/database_spec.rb +101 -1
  72. data/spec/core/dataset_spec.rb +42 -4
  73. data/spec/core/schema_spec.rb +13 -0
  74. data/spec/extensions/active_model_spec.rb +34 -11
  75. data/spec/extensions/caching_spec.rb +2 -0
  76. data/spec/extensions/instance_filters_spec.rb +55 -0
  77. data/spec/extensions/spec_helper.rb +2 -0
  78. data/spec/integration/dataset_test.rb +12 -1
  79. data/spec/integration/model_test.rb +12 -0
  80. data/spec/integration/plugin_test.rb +61 -1
  81. data/spec/integration/schema_test.rb +14 -3
  82. data/spec/model/base_spec.rb +27 -0
  83. data/spec/model/plugins_spec.rb +0 -22
  84. data/spec/model/record_spec.rb +32 -1
  85. data/spec/model/spec_helper.rb +2 -0
  86. metadata +14 -3
  87. data/lib/sequel/dataset/convenience.rb +0 -326
@@ -130,11 +130,6 @@ module Sequel
130
130
  clone(:sequence=>s)
131
131
  end
132
132
 
133
- # Oracle does not support DISTINCT ON
134
- def supports_distinct_on?
135
- false
136
- end
137
-
138
133
  # Oracle does not support INTERSECT ALL or EXCEPT ALL
139
134
  def supports_intersect_except_all?
140
135
  false
@@ -113,24 +113,19 @@ module Sequel
113
113
  # is true.
114
114
  def apply_connection_settings
115
115
  if Postgres.force_standard_strings
116
- sql = "SET standard_conforming_strings = ON"
117
- @db.log_info(sql)
118
116
  # This setting will only work on PostgreSQL 8.2 or greater
119
117
  # and we don't know the server version at this point, so
120
118
  # try it unconditionally and rescue any errors.
121
- execute(sql) rescue nil
119
+ execute("SET standard_conforming_strings = ON") rescue nil
122
120
  end
123
121
  if cmm = Postgres.client_min_messages
124
- sql = "SET client_min_messages = '#{cmm.to_s.upcase}'"
125
- @db.log_info(sql)
126
- execute(sql)
122
+ execute("SET client_min_messages = '#{cmm.to_s.upcase}'")
127
123
  end
128
124
  end
129
125
 
130
126
  # Get the last inserted value for the given sequence.
131
127
  def last_insert_id(sequence)
132
128
  sql = SELECT_CURRVAL % sequence
133
- @db.log_info(sql)
134
129
  execute(sql) do |r|
135
130
  val = single_value(r)
136
131
  return val.to_i if val
@@ -140,7 +135,6 @@ module Sequel
140
135
  # Get the primary key for the given table.
141
136
  def primary_key(schema, table)
142
137
  sql = SELECT_PK[schema, table]
143
- @db.log_info(sql)
144
138
  execute(sql) do |r|
145
139
  return single_value(r)
146
140
  end
@@ -149,14 +143,12 @@ module Sequel
149
143
  # Get the primary key and sequence for the given table.
150
144
  def sequence(schema, table)
151
145
  sql = SELECT_SERIAL_SEQUENCE[schema, table]
152
- @db.log_info(sql)
153
146
  execute(sql) do |r|
154
147
  seq = single_value(r)
155
148
  return seq if seq
156
149
  end
157
150
 
158
151
  sql = SELECT_CUSTOM_SEQUENCE[schema, table]
159
- @db.log_info(sql)
160
152
  execute(sql) do |r|
161
153
  return single_value(r)
162
154
  end
@@ -501,6 +493,11 @@ module Sequel
501
493
  nil
502
494
  end
503
495
  end
496
+
497
+ # Don't log, since logging is done by the underlying connection.
498
+ def log_connection_execute(conn, sql)
499
+ conn.execute(sql)
500
+ end
504
501
 
505
502
  # Use a dollar sign instead of question mark for the argument
506
503
  # placeholder.
@@ -702,6 +699,11 @@ module Sequel
702
699
  [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
703
700
  end
704
701
 
702
+ # DISTINCT ON is a PostgreSQL extension
703
+ def supports_distinct_on?
704
+ true
705
+ end
706
+
705
707
  # PostgreSQL supports modifying joined datasets
706
708
  def supports_modifying_joins?
707
709
  true
@@ -758,7 +760,7 @@ module Sequel
758
760
 
759
761
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
760
762
  def literal_blob(v)
761
- "'#{v.gsub(/[\000-\037\047\134\177-\377]/){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
763
+ "'#{v.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
762
764
  end
763
765
 
764
766
  # PostgreSQL uses FALSE for false values
@@ -1,5 +1,8 @@
1
1
  module Sequel
2
2
  module SQLite
3
+ # No matter how you connect to SQLite, the following Database options
4
+ # can be used to set PRAGMAs on connections in a thread-safe manner:
5
+ # :auto_vacuum, :foreign_keys, :synchronous, and :temp_store.
3
6
  module DatabaseMethods
4
7
  AUTO_VACUUM = [:none, :full, :incremental].freeze
5
8
  PRIMARY_KEY_INDEX_RE = /\Asqlite_autoindex_/.freeze
@@ -21,7 +24,8 @@ module Sequel
21
24
  end
22
25
 
23
26
  # Set the auto_vacuum PRAGMA using the given symbol (:none, :full, or
24
- # :incremental).
27
+ # :incremental). See pragma_set. Consider using the :auto_vacuum
28
+ # Database option instead.
25
29
  def auto_vacuum=(value)
26
30
  value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
27
31
  pragma_set(:auto_vacuum, value)
@@ -31,6 +35,19 @@ module Sequel
31
35
  def database_type
32
36
  :sqlite
33
37
  end
38
+
39
+ # Boolean signifying the value of the foreign_keys PRAGMA, or nil
40
+ # if not using SQLite 3.6.19+.
41
+ def foreign_keys
42
+ pragma_get(:foreign_keys).to_i == 1 if sqlite_version >= 30619
43
+ end
44
+
45
+ # Set the foreign_keys PRAGMA using the given boolean value, if using
46
+ # SQLite 3.6.19+. If not using 3.6.19+, no error is raised. See pragma_set.
47
+ # Consider using the :foreign_keys Database option instead.
48
+ def foreign_keys=(value)
49
+ pragma_set(:foreign_keys, !!value ? 'on' : 'off') if sqlite_version >= 30619
50
+ end
34
51
 
35
52
  # Return a hash containing index information. Hash keys are index name symbols.
36
53
  # Values are subhashes with two keys, :columns and :unique. The value of :columns
@@ -61,13 +78,30 @@ module Sequel
61
78
  end
62
79
 
63
80
  # Set the value of the given PRAGMA to value.
81
+ #
82
+ # This method is not thread safe, and will not work correctly if there
83
+ # are multiple connections in the Database's connection pool. PRAGMA
84
+ # modifications should be done when the connection is created, using
85
+ # an option provided when creating the Database object.
64
86
  def pragma_set(name, value)
65
87
  execute_ddl("PRAGMA #{name} = #{value}")
66
88
  end
67
89
 
68
- # SQLite supports savepoints
90
+ # The version of the server as an integer, where 3.6.19 = 30619.
91
+ # If the server version can't be determined, 0 is used.
92
+ def sqlite_version
93
+ return @sqlite_version if defined?(@sqlite_version)
94
+ @sqlite_version = begin
95
+ v = get{sqlite_version{}}
96
+ [10000, 100, 1].zip(v.split('.')).inject(0){|a, m| a + m[0] * Integer(m[1])}
97
+ rescue
98
+ 0
99
+ end
100
+ end
101
+
102
+ # SQLite 3.6.8+ supports savepoints.
69
103
  def supports_savepoints?
70
- true
104
+ sqlite_version >= 30608
71
105
  end
72
106
 
73
107
  # A symbol signifying the value of the synchronous PRAGMA.
@@ -75,7 +109,8 @@ module Sequel
75
109
  SYNCHRONOUS[pragma_get(:synchronous).to_i]
76
110
  end
77
111
 
78
- # Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full).
112
+ # Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full). See pragma_set.
113
+ # Consider using the :synchronous Database option instead.
79
114
  def synchronous=(value)
80
115
  value = SYNCHRONOUS.index(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
81
116
  pragma_set(:synchronous, value)
@@ -95,7 +130,8 @@ module Sequel
95
130
  TEMP_STORE[pragma_get(:temp_store).to_i]
96
131
  end
97
132
 
98
- # Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory).
133
+ # Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory). See pragma_set.
134
+ # Consider using the :temp_store Database option instead.
99
135
  def temp_store=(value)
100
136
  value = TEMP_STORE.index(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
101
137
  pragma_set(:temp_store, value)
@@ -195,6 +231,21 @@ module Sequel
195
231
  def identifier_output_method_default
196
232
  nil
197
233
  end
234
+
235
+ # Array of PRAGMA SQL statements based on the Database options that should be applied to
236
+ # new connections.
237
+ def connection_pragmas
238
+ ps = []
239
+ v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
240
+ ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
241
+ [[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
242
+ if v = opts[prag]
243
+ raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
244
+ ps << "PRAGMA #{prag} = #{v}"
245
+ end
246
+ end
247
+ ps
248
+ end
198
249
 
199
250
  # Parse the output of the table_info pragma
200
251
  def parse_pragma(table_name, opts)
@@ -265,11 +316,6 @@ module Sequel
265
316
  @opts[:where] ? super : filter(1=>1).delete
266
317
  end
267
318
 
268
- # SQLite does not support DISTINCT ON
269
- def supports_distinct_on?
270
- false
271
- end
272
-
273
319
  # Return an array of strings specifying a query explanation for a SELECT of the
274
320
  # current dataset.
275
321
  def explain
@@ -37,6 +37,8 @@ module Sequel
37
37
  db.busy_timeout(opts.fetch(:timeout, 5000))
38
38
  db.type_translation = true
39
39
 
40
+ connection_pragmas.each{|s| log_yield(s){db.execute_batch(s)}}
41
+
40
42
  # Handle datetimes with Sequel.datetime_class
41
43
  prok = proc do |t,v|
42
44
  v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
@@ -70,32 +72,37 @@ module Sequel
70
72
 
71
73
  # Run the given SQL with the given arguments and return the number of changed rows.
72
74
  def execute_dui(sql, opts={})
73
- _execute(sql, opts){|conn| conn.execute_batch(sql, opts[:arguments]); conn.changes}
75
+ _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute_batch(sql, opts[:arguments])}; conn.changes}
74
76
  end
75
77
 
76
78
  # Run the given SQL with the given arguments and return the last inserted row id.
77
79
  def execute_insert(sql, opts={})
78
- _execute(sql, opts){|conn| conn.execute(sql, opts[:arguments]); conn.last_insert_row_id}
80
+ _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute(sql, opts[:arguments])}; conn.last_insert_row_id}
79
81
  end
80
82
 
81
83
  # Run the given SQL with the given arguments and yield each row.
82
- def execute(sql, opts={}, &block)
83
- _execute(sql, opts){|conn| conn.query(sql, opts[:arguments], &block)}
84
+ def execute(sql, opts={})
85
+ _execute(opts) do |conn|
86
+ begin
87
+ yield(result = log_yield(sql, opts[:arguments]){conn.query(sql, opts[:arguments])})
88
+ ensure
89
+ result.close if result
90
+ end
91
+ end
84
92
  end
85
93
 
86
94
  # Run the given SQL with the given arguments and return the first value of the first row.
87
95
  def single_value(sql, opts={})
88
- _execute(sql, opts){|conn| conn.get_first_value(sql, opts[:arguments])}
96
+ _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.get_first_value(sql, opts[:arguments])}}
89
97
  end
90
98
 
91
99
  private
92
100
 
93
- # Log the SQL and the arguments, and yield an available connection. Rescue
101
+ # Yield an available connection. Rescue
94
102
  # any SQLite3::Exceptions and turn them into DatabaseErrors.
95
- def _execute(sql, opts)
103
+ def _execute(opts, &block)
96
104
  begin
97
- log_info(sql, opts[:arguments])
98
- synchronize(opts[:server]){|conn| yield conn}
105
+ synchronize(opts[:server], &block)
99
106
  rescue SQLite3::Exception => e
100
107
  raise_error(e)
101
108
  end
@@ -60,12 +60,16 @@ class Sequel::ConnectionPool
60
60
  # Instantiates a connection pool with the given options. The block is called
61
61
  # with a single symbol (specifying the server/shard to use) every time a new
62
62
  # connection is needed. The following options are respected for all connection
63
- # pools: #
63
+ # pools:
64
+ # * :after_connect - The proc called after each new connection is made, with the
65
+ # connection object, useful for customizations that you want to apply to all
66
+ # connections.
64
67
  # * :disconnection_proc - The proc called when removing connections from the pool,
65
68
  # which is passed the connection to disconnect.
66
69
  def initialize(opts={}, &block)
67
70
  raise(Sequel::Error, "No connection proc specified") unless @connection_proc = block
68
71
  @disconnection_proc = opts[:disconnection_proc]
72
+ @after_connect = opts[:after_connect]
69
73
  end
70
74
 
71
75
  # Alias for size, not aliased directly for ease of subclass implementation
@@ -85,6 +89,7 @@ class Sequel::ConnectionPool
85
89
  def make_new(server)
86
90
  begin
87
91
  conn = @connection_proc.call(server)
92
+ @after_connect.call(conn) if @after_connect
88
93
  rescue Exception=>exception
89
94
  raise Sequel.convert_exception_class(exception, Sequel::DatabaseConnectionError)
90
95
  end
@@ -10,6 +10,7 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
10
10
 
11
11
  # Disconnect the connection from the database.
12
12
  def disconnect(opts=nil, &block)
13
+ return unless @conn
13
14
  block ||= @disconnection_proc
14
15
  block.call(@conn) if block
15
16
  @conn = nil
data/lib/sequel/core.rb CHANGED
@@ -46,6 +46,9 @@
46
46
  #
47
47
  # You can set the SEQUEL_NO_CORE_EXTENSIONS constant or environment variable to have
48
48
  # Sequel not extend the core classes.
49
+ #
50
+ # For a more expanded introduction, see the {README}[link:files/README_rdoc.html].
51
+ # For a quicker introduction, see the {cheat sheet}[link:files/doc/cheat_sheet_rdoc.html].
49
52
  module Sequel
50
53
  @convert_two_digit_years = true
51
54
  @datetime_class = Time
@@ -117,6 +120,9 @@ module Sequel
117
120
  # closed when the block exits. For example:
118
121
  #
119
122
  # Sequel.connect('sqlite://blog.db'){|db| puts db[:users].count}
123
+ #
124
+ # For details, see the {"Connecting to a Database" guide}[link:files/doc/opening_databases_rdoc.html].
125
+ # To set up a master/slave or sharded database connection, see the {"Master/Slave Databases and Sharding" guide}[link:files/doc/sharding_rdoc.html].
120
126
  def self.connect(*args, &block)
121
127
  Database.connect(*args, &block)
122
128
  end
@@ -280,7 +286,6 @@ module Sequel
280
286
 
281
287
  require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
282
288
  require(%w"schema_generator schema_methods schema_sql", 'database')
283
- require(%w"actions convenience features graph prepared_statements query sql", 'dataset')
284
289
  require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
285
290
 
286
291
  # Add the database adapter class methods to Sequel via metaprogramming
@@ -50,6 +50,10 @@ module Sequel
50
50
  # The default schema to use, generally should be nil.
51
51
  attr_accessor :default_schema
52
52
 
53
+ # Numeric specifying the duration beyond which queries are logged at warn
54
+ # level instead of info level.
55
+ attr_accessor :log_warn_duration
56
+
53
57
  # Array of SQL loggers to use for this database
54
58
  attr_accessor :loggers
55
59
 
@@ -82,13 +86,14 @@ module Sequel
82
86
  @opts ||= opts
83
87
  @opts = connection_pool_default_options.merge(@opts)
84
88
  @loggers = Array(@opts[:logger]) + Array(@opts[:loggers])
89
+ self.log_warn_duration = @opts[:log_warn_duration]
85
90
  @opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
86
91
  block ||= proc{|server| connect(server)}
87
92
  @opts[:servers] = {} if @opts[:servers].is_a?(String)
88
93
 
89
- @opts[:single_threaded] = @single_threaded = @opts.include?(:single_threaded) ? typecast_value_boolean(@opts[:single_threaded]) : @@single_threaded
94
+ @opts[:single_threaded] = @single_threaded = typecast_value_boolean(@opts.fetch(:single_threaded, @@single_threaded))
90
95
  @schemas = {}
91
- @default_schema = opts.include?(:default_schema) ? @opts[:default_schema] : default_schema_default
96
+ @default_schema = @opts.fetch(:default_schema, default_schema_default)
92
97
  @prepared_statements = {}
93
98
  @transactions = []
94
99
  @identifier_input_method = nil
@@ -129,7 +134,7 @@ module Sequel
129
134
  end
130
135
 
131
136
  # Connects to a database. See Sequel.connect.
132
- def self.connect(conn_string, opts = {}, &block)
137
+ def self.connect(conn_string, opts = {})
133
138
  case conn_string
134
139
  when String
135
140
  if match = /\A(jdbc|do):/o.match(conn_string)
@@ -157,18 +162,17 @@ module Sequel
157
162
  m[k.to_sym] = v
158
163
  m
159
164
  end
160
- if block
161
- result = nil
162
- begin
163
- result = yield(db = c.new(opts))
164
- ensure
165
+ begin
166
+ db = c.new(opts)
167
+ db.test_connection if opts[:test] && db.send(:typecast_value_boolean, opts[:test])
168
+ result = yield(db) if block_given?
169
+ ensure
170
+ if block_given?
165
171
  db.disconnect if db
166
172
  ::Sequel::DATABASES.delete(db)
167
173
  end
168
- result
169
- else
170
- c.new(opts)
171
174
  end
175
+ block_given? ? result : db
172
176
  end
173
177
 
174
178
  # The method to call on identifiers going into the database
@@ -389,7 +393,7 @@ module Sequel
389
393
  def identifier_input_method
390
394
  case @identifier_input_method
391
395
  when nil
392
- @identifier_input_method = @opts.include?(:identifier_input_method) ? @opts[:identifier_input_method] : (@@identifier_input_method.nil? ? identifier_input_method_default : @@identifier_input_method)
396
+ @identifier_input_method = @opts.fetch(:identifier_input_method, (@@identifier_input_method.nil? ? identifier_input_method_default : @@identifier_input_method))
393
397
  @identifier_input_method == "" ? nil : @identifier_input_method
394
398
  when ""
395
399
  nil
@@ -408,7 +412,7 @@ module Sequel
408
412
  def identifier_output_method
409
413
  case @identifier_output_method
410
414
  when nil
411
- @identifier_output_method = @opts.include?(:identifier_output_method) ? @opts[:identifier_output_method] : (@@identifier_output_method.nil? ? identifier_output_method_default : @@identifier_output_method)
415
+ @identifier_output_method = @opts.fetch(:identifier_output_method, (@@identifier_output_method.nil? ? identifier_output_method_default : @@identifier_output_method))
412
416
  @identifier_output_method == "" ? nil : @identifier_output_method
413
417
  when ""
414
418
  nil
@@ -435,11 +439,25 @@ module Sequel
435
439
  schema_utility_dataset.literal(v)
436
440
  end
437
441
 
438
- # Log a message at level info to all loggers. All SQL logging
439
- # goes through this method.
442
+ # Log a message at level info to all loggers.
440
443
  def log_info(message, args=nil)
441
- message = "#{message}; #{args.inspect}" if args
442
- @loggers.each{|logger| logger.info(message)}
444
+ log_each(:info, args ? "#{message}; #{args.inspect}" : message)
445
+ end
446
+
447
+ # Yield to the block, logging any errors at error level to all loggers,
448
+ # and all other queries with the duration at warn or info level.
449
+ def log_yield(sql, args=nil)
450
+ return yield if @loggers.empty?
451
+ sql = "#{sql}; #{args.inspect}" if args
452
+ start = Time.now
453
+ begin
454
+ yield
455
+ rescue => e
456
+ log_each(:error, "#{e.class}: #{e.message.strip}: #{sql}")
457
+ raise
458
+ ensure
459
+ log_duration(Time.now - start, sql) unless e
460
+ end
443
461
  end
444
462
 
445
463
  # Remove any existing loggers and just use the given logger.
@@ -456,7 +474,7 @@ module Sequel
456
474
  # Returns true if the database quotes identifiers.
457
475
  def quote_identifiers?
458
476
  return @quote_identifiers unless @quote_identifiers.nil?
459
- @quote_identifiers = @opts.include?(:quote_identifiers) ? @opts[:quote_identifiers] : (@@quote_identifiers.nil? ? quote_identifiers_default : @@quote_identifiers)
477
+ @quote_identifiers = @opts.fetch(:quote_identifiers, (@@quote_identifiers.nil? ? quote_identifiers_default : @@quote_identifiers))
460
478
  end
461
479
 
462
480
  # Dynamically remove existing servers from the connection pool. Intended for
@@ -818,8 +836,19 @@ module Sequel
818
836
  # Log the given SQL and then execute it on the connection, used by
819
837
  # the transaction code.
820
838
  def log_connection_execute(conn, sql)
821
- log_info(sql)
822
- conn.send(connection_execute_method, sql)
839
+ log_yield(sql){conn.send(connection_execute_method, sql)}
840
+ end
841
+
842
+ # Log message with message prefixed by duration at info level, or
843
+ # warn level if duration is greater than log_warn_duration.
844
+ def log_duration(duration, message)
845
+ log_each((lwd = log_warn_duration and duration >= lwd) ? :warn : :info, "(#{sprintf('%0.6fs', duration)}) #{message}")
846
+ end
847
+
848
+ # Log message at level (which should be :error, :warn, or :info)
849
+ # to all loggers.
850
+ def log_each(level, message)
851
+ @loggers.each{|logger| logger.send(level, message)}
823
852
  end
824
853
 
825
854
  # Return a dataset that uses the default identifier input and output methods
@@ -924,11 +953,11 @@ module Sequel
924
953
  :boolean
925
954
  when /\A(real|float|double( precision)?)\z/io
926
955
  :float
927
- when /\A(((numeric|decimal)(\(\d+,\d+\))?)|(small)?money)\z/io
928
- :decimal
956
+ when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
957
+ $1 && $1 == '0' ? :integer : :decimal
929
958
  when /bytea|blob|image|(var)?binary/io
930
959
  :blob
931
- when /\Aenum/
960
+ when /\Aenum/io
932
961
  :enum
933
962
  end
934
963
  end