sequel 3.10.0 → 3.11.0

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