sequel 3.11.0 → 3.12.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 (136) hide show
  1. data/CHANGELOG +70 -0
  2. data/Rakefile +1 -1
  3. data/doc/active_record.rdoc +896 -0
  4. data/doc/advanced_associations.rdoc +46 -31
  5. data/doc/association_basics.rdoc +14 -9
  6. data/doc/dataset_basics.rdoc +3 -3
  7. data/doc/migration.rdoc +1011 -0
  8. data/doc/model_hooks.rdoc +198 -0
  9. data/doc/querying.rdoc +811 -86
  10. data/doc/release_notes/3.12.0.txt +304 -0
  11. data/doc/sharding.rdoc +17 -0
  12. data/doc/sql.rdoc +537 -0
  13. data/doc/validations.rdoc +501 -0
  14. data/lib/sequel/adapters/jdbc.rb +19 -27
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
  16. data/lib/sequel/adapters/mysql.rb +5 -4
  17. data/lib/sequel/adapters/odbc.rb +3 -2
  18. data/lib/sequel/adapters/shared/mssql.rb +7 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +2 -8
  21. data/lib/sequel/adapters/shared/sqlite.rb +2 -5
  22. data/lib/sequel/adapters/sqlite.rb +4 -4
  23. data/lib/sequel/core.rb +0 -1
  24. data/lib/sequel/database.rb +2 -1060
  25. data/lib/sequel/database/connecting.rb +227 -0
  26. data/lib/sequel/database/dataset.rb +58 -0
  27. data/lib/sequel/database/dataset_defaults.rb +127 -0
  28. data/lib/sequel/database/logging.rb +62 -0
  29. data/lib/sequel/database/misc.rb +246 -0
  30. data/lib/sequel/database/query.rb +390 -0
  31. data/lib/sequel/database/schema_generator.rb +7 -3
  32. data/lib/sequel/database/schema_methods.rb +351 -7
  33. data/lib/sequel/dataset/actions.rb +9 -2
  34. data/lib/sequel/dataset/misc.rb +6 -2
  35. data/lib/sequel/dataset/mutation.rb +3 -11
  36. data/lib/sequel/dataset/query.rb +49 -6
  37. data/lib/sequel/exceptions.rb +3 -0
  38. data/lib/sequel/extensions/migration.rb +395 -113
  39. data/lib/sequel/extensions/schema_dumper.rb +21 -13
  40. data/lib/sequel/model.rb +27 -25
  41. data/lib/sequel/model/associations.rb +72 -34
  42. data/lib/sequel/model/base.rb +74 -18
  43. data/lib/sequel/model/errors.rb +8 -1
  44. data/lib/sequel/plugins/active_model.rb +8 -0
  45. data/lib/sequel/plugins/association_pks.rb +87 -0
  46. data/lib/sequel/plugins/association_proxies.rb +8 -0
  47. data/lib/sequel/plugins/boolean_readers.rb +12 -6
  48. data/lib/sequel/plugins/caching.rb +14 -7
  49. data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
  50. data/lib/sequel/plugins/composition.rb +2 -1
  51. data/lib/sequel/plugins/force_encoding.rb +10 -7
  52. data/lib/sequel/plugins/hook_class_methods.rb +12 -11
  53. data/lib/sequel/plugins/identity_map.rb +9 -0
  54. data/lib/sequel/plugins/instance_hooks.rb +23 -13
  55. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  56. data/lib/sequel/plugins/many_through_many.rb +18 -4
  57. data/lib/sequel/plugins/nested_attributes.rb +1 -0
  58. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  59. data/lib/sequel/plugins/rcte_tree.rb +9 -8
  60. data/lib/sequel/plugins/schema.rb +8 -0
  61. data/lib/sequel/plugins/serialization.rb +1 -3
  62. data/lib/sequel/plugins/sharding.rb +135 -0
  63. data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
  64. data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
  65. data/lib/sequel/plugins/string_stripper.rb +26 -0
  66. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
  67. data/lib/sequel/plugins/timestamps.rb +15 -2
  68. data/lib/sequel/plugins/touch.rb +13 -0
  69. data/lib/sequel/plugins/update_primary_key.rb +48 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +8 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +17 -20
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/postgres_spec.rb +5 -5
  75. data/spec/core/core_sql_spec.rb +17 -1
  76. data/spec/core/database_spec.rb +17 -5
  77. data/spec/core/dataset_spec.rb +31 -8
  78. data/spec/core/schema_generator_spec.rb +8 -1
  79. data/spec/core/schema_spec.rb +13 -0
  80. data/spec/extensions/association_pks_spec.rb +85 -0
  81. data/spec/extensions/hook_class_methods_spec.rb +9 -9
  82. data/spec/extensions/migration_spec.rb +339 -219
  83. data/spec/extensions/schema_dumper_spec.rb +28 -17
  84. data/spec/extensions/sharding_spec.rb +272 -0
  85. data/spec/extensions/single_table_inheritance_spec.rb +92 -4
  86. data/spec/extensions/skip_create_refresh_spec.rb +17 -0
  87. data/spec/extensions/string_stripper_spec.rb +23 -0
  88. data/spec/extensions/update_primary_key_spec.rb +65 -0
  89. data/spec/extensions/validation_class_methods_spec.rb +5 -5
  90. data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
  91. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
  92. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  93. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  94. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
  95. data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
  96. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
  97. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
  98. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
  99. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
  100. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
  101. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
  102. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
  103. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
  104. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  105. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
  106. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
  107. data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
  108. data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
  109. data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
  110. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  111. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
  112. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  113. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
  114. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  115. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
  116. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
  117. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  118. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  119. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
  120. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
  121. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
  122. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
  123. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
  124. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
  125. data/spec/integration/eager_loader_test.rb +20 -20
  126. data/spec/integration/migrator_test.rb +187 -0
  127. data/spec/integration/plugin_test.rb +150 -0
  128. data/spec/integration/schema_test.rb +13 -2
  129. data/spec/model/associations_spec.rb +41 -14
  130. data/spec/model/base_spec.rb +69 -0
  131. data/spec/model/eager_loading_spec.rb +7 -3
  132. data/spec/model/record_spec.rb +79 -4
  133. data/spec/model/validations_spec.rb +21 -9
  134. metadata +66 -5
  135. data/doc/schema.rdoc +0 -36
  136. data/lib/sequel/database/schema_sql.rb +0 -320
@@ -159,7 +159,8 @@ module Sequel
159
159
  args.concat([opts[:user], opts[:password]]) if opts[:user] && opts[:password]
160
160
  begin
161
161
  JavaSQL::DriverManager.getConnection(*args)
162
- rescue
162
+ rescue => e
163
+ raise e unless driver
163
164
  # If the DriverManager can't get the connection - use the connect
164
165
  # method of the driver. (This happens under Tomcat for instance)
165
166
  props = java.util.Properties.new
@@ -167,7 +168,7 @@ module Sequel
167
168
  props.setProperty("user", opts[:user])
168
169
  props.setProperty("password", opts[:password])
169
170
  end
170
- driver.new.connect(args[0], props)
171
+ driver.new.connect(args[0], props) rescue (raise e)
171
172
  end
172
173
  end
173
174
  setup_connection(conn)
@@ -184,8 +185,7 @@ module Sequel
184
185
  return call_sproc(sql, opts, &block) if opts[:sproc]
185
186
  return execute_prepared_statement(sql, opts, &block) if [Symbol, Dataset].any?{|c| sql.is_a?(c)}
186
187
  synchronize(opts[:server]) do |conn|
187
- stmt = conn.createStatement
188
- begin
188
+ statement(conn) do |stmt|
189
189
  if block_given?
190
190
  yield log_yield(sql){stmt.executeQuery(sql)}
191
191
  else
@@ -205,10 +205,6 @@ module Sequel
205
205
  log_yield(sql){stmt.executeUpdate(sql)}
206
206
  end
207
207
  end
208
- rescue NativeException, JavaSQL::SQLException => e
209
- raise_error(e)
210
- ensure
211
- stmt.close
212
208
  end
213
209
  end
214
210
  end
@@ -226,10 +222,7 @@ module Sequel
226
222
  execute(sql, {:type=>:insert}.merge(opts))
227
223
  end
228
224
 
229
- # Return a hash containing index information. Hash keys are index name symbols.
230
- # Values are subhashes with two keys, :columns and :unique. The value of :columns
231
- # is an array of symbols of column names. The value of :unique is true or false
232
- # depending on if the index is unique.
225
+ # Use the JDBC metadata to get the index information for the table.
233
226
  def indexes(table, opts={})
234
227
  m = output_identifier_meth
235
228
  im = input_identifier_meth
@@ -272,12 +265,6 @@ module Sequel
272
265
 
273
266
  private
274
267
 
275
- # JDBC uses a statement object to execute SQL on the database
276
- def begin_transaction(conn)
277
- conn = conn.createStatement unless supports_savepoints?
278
- super
279
- end
280
-
281
268
  # Close given adapter connections
282
269
  def disconnect_connection(c)
283
270
  c.close
@@ -354,6 +341,12 @@ module Sequel
354
341
  ts
355
342
  end
356
343
 
344
+ # Log the given SQL and then execute it on the connection, used by
345
+ # the transaction code.
346
+ def log_connection_execute(conn, sql)
347
+ statement(conn){|s| log_yield(sql){s.execute(sql)}}
348
+ end
349
+
357
350
  # By default, there is no support for determining the last inserted
358
351
  # id, so return nil. This method should be overridden in
359
352
  # sub adapters.
@@ -379,12 +372,6 @@ module Sequel
379
372
  super(exception, {:disconnect => cause.respond_to?(:getSQLState) && cause.getSQLState =~ /^08/}.merge(opts))
380
373
  end
381
374
 
382
- # Close the given statement when removing the transaction
383
- def remove_transaction(stmt)
384
- stmt.close if stmt && !supports_savepoints?
385
- super
386
- end
387
-
388
375
  # Java being java, you need to specify the type of each argument
389
376
  # for the prepared statement, and bind it individually. This
390
377
  # guesses which JDBC method to use, and hopefully JRuby will convert
@@ -448,9 +435,14 @@ module Sequel
448
435
  ts
449
436
  end
450
437
 
451
- # Create a statement object to execute transaction statements.
452
- def transaction_statement_object(conn)
453
- conn.createStatement
438
+ # Yield a new statement object, and ensure that it is closed before returning.
439
+ def statement(conn)
440
+ stmt = conn.createStatement
441
+ yield stmt
442
+ rescue NativeException, JavaSQL::SQLException => e
443
+ raise_error(e)
444
+ ensure
445
+ stmt.close if stmt
454
446
  end
455
447
 
456
448
  # This method determines whether or not to add
@@ -78,13 +78,6 @@ module Sequel
78
78
  def last_insert_id(conn, opts)
79
79
  insert_result(conn, opts[:table], opts[:values])
80
80
  end
81
-
82
- # Override shared postgresql adapter method to actually log,
83
- # since on JDBC the first argument is a statement and not a
84
- # connection, so it wouldn't be logged otherwise.
85
- def log_connection_execute(stmt, sql)
86
- log_yield(sql){stmt.execute(sql)}
87
- end
88
81
  end
89
82
 
90
83
  # Dataset subclass used for datasets that connect to PostgreSQL via JDBC.
@@ -99,10 +99,6 @@ module Sequel
99
99
  conn = Mysql.init
100
100
  conn.options(Mysql::READ_DEFAULT_GROUP, opts[:config_default_group] || "client")
101
101
  conn.options(Mysql::OPT_LOCAL_INFILE, opts[:config_local_infile]) if opts.has_key?(:config_local_infile)
102
- if encoding = opts[:encoding] || opts[:charset]
103
- # set charset _before_ the connect. using an option instead of "SET (NAMES|CHARACTER_SET_*)" works across reconnects
104
- conn.options(Mysql::SET_CHARSET_NAME, encoding)
105
- end
106
102
  conn.real_connect(
107
103
  opts[:host] || 'localhost',
108
104
  opts[:user],
@@ -114,6 +110,11 @@ module Sequel
114
110
  Mysql::CLIENT_MULTI_STATEMENTS +
115
111
  (opts[:compress] == false ? 0 : Mysql::CLIENT_COMPRESS)
116
112
  )
113
+ if encoding = opts[:encoding] || opts[:charset]
114
+ # Setting encoding before the connect appears not to work
115
+ # with READ_DEFAULT_GROUP, so set it afterwards.
116
+ conn.options(Mysql::SET_CHARSET_NAME, encoding)
117
+ end
117
118
 
118
119
  # increase timeout so mysql server doesn't disconnect us
119
120
  conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}")
@@ -7,6 +7,7 @@ module Sequel
7
7
 
8
8
  GUARDED_DRV_NAME = /^\{.+\}$/.freeze
9
9
  DRV_NAME_GUARDS = '{%s}'.freeze
10
+ DISCONNECT_ERRORS = /\A08S01/.freeze
10
11
 
11
12
  def initialize(opts)
12
13
  super
@@ -50,7 +51,7 @@ module Sequel
50
51
  r = log_yield(sql){conn.run(sql)}
51
52
  yield(r) if block_given?
52
53
  rescue ::ODBC::Error, ArgumentError => e
53
- raise_error(e)
54
+ raise_error(e, :disconnect=>DISCONNECT_ERRORS.match(e.message))
54
55
  ensure
55
56
  r.drop if r
56
57
  end
@@ -63,7 +64,7 @@ module Sequel
63
64
  begin
64
65
  log_yield(sql){conn.do(sql)}
65
66
  rescue ::ODBC::Error, ArgumentError => e
66
- raise_error(e)
67
+ raise_error(e, :disconnect=>DISCONNECT_ERRORS.match(e.message))
67
68
  end
68
69
  end
69
70
  end
@@ -10,7 +10,6 @@ module Sequel
10
10
  SQL_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION".freeze
11
11
  SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
12
12
  SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
13
- TEMPORARY = "#".freeze
14
13
 
15
14
  # The types to check for 0 scale to transform :decimal types
16
15
  # to :integer.
@@ -100,6 +99,13 @@ module Sequel
100
99
  def commit_transaction_sql
101
100
  SQL_COMMIT
102
101
  end
102
+
103
+ # MSSQL uses the name of the table to decide the difference between
104
+ # a regular and temporary table, with temporary table names starting with
105
+ # a #.
106
+ def create_table_sql(name, generator, options)
107
+ "CREATE TABLE #{quote_schema_table(options[:temp] ? "##{name}" : name)} (#{column_list_sql(generator)})"
108
+ end
103
109
 
104
110
  # The SQL to drop an index for the table.
105
111
  def drop_index_sql(table, op)
@@ -153,11 +159,6 @@ module Sequel
153
159
  end
154
160
  end
155
161
 
156
- # SQL fragment for marking a table as temporary
157
- def temporary_table_sql
158
- TEMPORARY
159
- end
160
-
161
162
  # MSSQL has both datetime and timestamp classes, most people are going
162
163
  # to want datetime
163
164
  def type_literal_generic_datetime(column)
@@ -35,13 +35,8 @@ module Sequel
35
35
  :mysql
36
36
  end
37
37
 
38
- # Return a hash containing index information. Hash keys are index name symbols.
39
- # Values are subhashes with two keys, :columns and :unique. The value of :columns
40
- # is an array of symbols of column names. The value of :unique is true or false
41
- # depending on if the index is unique.
42
- #
43
- # Does not include the primary key index or indexes on partial keys.
44
- def indexes(table)
38
+ # Use SHOW INDEX FROM to get the index information for the table.
39
+ def indexes(table, opts={})
45
40
  indexes = {}
46
41
  remove_indexes = []
47
42
  m = output_identifier_meth
@@ -253,14 +253,8 @@ module Sequel
253
253
  self << drop_trigger_sql(table, name, opts)
254
254
  end
255
255
 
256
- # Return a hash containing index information. Hash keys are index name symbols.
257
- # Values are subhashes with two keys, :columns and :unique. The value of :columns
258
- # is an array of symbols of column names. The value of :unique is true or false
259
- # depending on if the index is unique.
260
- #
261
- # This will not output any partial indexes, indexes that aren't valid or ready,
262
- # or indexes that contain anything other than simple column references.
263
- def indexes(table)
256
+ # Use the pg_* system tables to determine indexes on a table
257
+ def indexes(table, opts={})
264
258
  m = output_identifier_meth
265
259
  im = input_identifier_meth
266
260
  schema, table = schema_and_table(table)
@@ -49,11 +49,8 @@ module Sequel
49
49
  pragma_set(:foreign_keys, !!value ? 'on' : 'off') if sqlite_version >= 30619
50
50
  end
51
51
 
52
- # Return a hash containing index information. Hash keys are index name symbols.
53
- # Values are subhashes with two keys, :columns and :unique. The value of :columns
54
- # is an array of symbols of column names. The value of :unique is true or false
55
- # depending on if the index is unique.
56
- def indexes(table)
52
+ # Use the index_list and index_info PRAGMAs to determine the indexes on the table.
53
+ def indexes(table, opts={})
57
54
  m = output_identifier_meth
58
55
  im = input_identifier_meth
59
56
  indexes = {}
@@ -72,19 +72,19 @@ module Sequel
72
72
 
73
73
  # Run the given SQL with the given arguments and return the number of changed rows.
74
74
  def execute_dui(sql, opts={})
75
- _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute_batch(sql, opts[:arguments])}; conn.changes}
75
+ _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute_batch(sql, opts.fetch(:arguments, []))}; conn.changes}
76
76
  end
77
77
 
78
78
  # Run the given SQL with the given arguments and return the last inserted row id.
79
79
  def execute_insert(sql, opts={})
80
- _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute(sql, opts[:arguments])}; conn.last_insert_row_id}
80
+ _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute(sql, opts.fetch(:arguments, []))}; conn.last_insert_row_id}
81
81
  end
82
82
 
83
83
  # Run the given SQL with the given arguments and yield each row.
84
84
  def execute(sql, opts={})
85
85
  _execute(opts) do |conn|
86
86
  begin
87
- yield(result = log_yield(sql, opts[:arguments]){conn.query(sql, opts[:arguments])})
87
+ yield(result = log_yield(sql, opts[:arguments]){conn.query(sql, opts.fetch(:arguments, []))})
88
88
  ensure
89
89
  result.close if result
90
90
  end
@@ -93,7 +93,7 @@ module Sequel
93
93
 
94
94
  # Run the given SQL with the given arguments and return the first value of the first row.
95
95
  def single_value(sql, opts={})
96
- _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.get_first_value(sql, opts[:arguments])}}
96
+ _execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.get_first_value(sql, opts.fetch(:arguments, []))}}
97
97
  end
98
98
 
99
99
  private
data/lib/sequel/core.rb CHANGED
@@ -285,7 +285,6 @@ module Sequel
285
285
  private_class_method :adapter_method, :def_adapter_method
286
286
 
287
287
  require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
288
- require(%w"schema_generator schema_methods schema_sql", 'database')
289
288
  require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
290
289
 
291
290
  # Add the database adapter class methods to Sequel via metaprogramming
@@ -15,1065 +15,7 @@ module Sequel
15
15
  class Database
16
16
  extend Metaprogramming
17
17
  include Metaprogramming
18
-
19
- # Array of supported database adapters
20
- ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql odbc openbase oracle postgres sqlite'.collect{|x| x.to_sym}
21
-
22
- SQL_BEGIN = 'BEGIN'.freeze
23
- SQL_COMMIT = 'COMMIT'.freeze
24
- SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
25
- SQL_ROLLBACK = 'ROLLBACK'.freeze
26
- SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TO SAVEPOINT autopoint_%d'.freeze
27
- SQL_SAVEPOINT = 'SAVEPOINT autopoint_%d'.freeze
28
-
29
- TRANSACTION_BEGIN = 'Transaction.begin'.freeze
30
- TRANSACTION_COMMIT = 'Transaction.commit'.freeze
31
- TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
32
-
33
- POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
34
- MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
35
- MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
36
- STRING_DEFAULT_RE = /\A'(.*)'\z/
37
-
38
- # The identifier input method to use by default
39
- @@identifier_input_method = nil
40
-
41
- # The identifier output method to use by default
42
- @@identifier_output_method = nil
43
-
44
- # Whether to use the single threaded connection pool by default
45
- @@single_threaded = false
46
-
47
- # Whether to quote identifiers (columns and tables) by default
48
- @@quote_identifiers = nil
49
-
50
- # The default schema to use, generally should be nil.
51
- attr_accessor :default_schema
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
-
57
- # Array of SQL loggers to use for this database
58
- attr_accessor :loggers
59
-
60
- # The options for this database
61
- attr_reader :opts
62
-
63
- # The connection pool for this database
64
- attr_reader :pool
65
-
66
- # The prepared statement objects for this database, keyed by name
67
- attr_reader :prepared_statements
68
-
69
- # Constructs a new instance of a database connection with the specified
70
- # options hash.
71
- #
72
- # Sequel::Database is an abstract class that is not useful by itself.
73
- #
74
- # Takes the following options:
75
- # * :default_schema : The default schema to use, should generally be nil
76
- # * :disconnection_proc: A proc used to disconnect the connection.
77
- # * :identifier_input_method: A string method symbol to call on identifiers going into the database
78
- # * :identifier_output_method: A string method symbol to call on identifiers coming from the database
79
- # * :loggers : An array of loggers to use.
80
- # * :quote_identifiers : Whether to quote identifiers
81
- # * :single_threaded : Whether to use a single-threaded connection pool
82
- #
83
- # All options given are also passed to the ConnectionPool. If a block
84
- # is given, it is used as the connection_proc for the ConnectionPool.
85
- def initialize(opts = {}, &block)
86
- @opts ||= opts
87
- @opts = connection_pool_default_options.merge(@opts)
88
- @loggers = Array(@opts[:logger]) + Array(@opts[:loggers])
89
- self.log_warn_duration = @opts[:log_warn_duration]
90
- @opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
91
- block ||= proc{|server| connect(server)}
92
- @opts[:servers] = {} if @opts[:servers].is_a?(String)
93
-
94
- @opts[:single_threaded] = @single_threaded = typecast_value_boolean(@opts.fetch(:single_threaded, @@single_threaded))
95
- @schemas = {}
96
- @default_schema = @opts.fetch(:default_schema, default_schema_default)
97
- @prepared_statements = {}
98
- @transactions = []
99
- @identifier_input_method = nil
100
- @identifier_output_method = nil
101
- @quote_identifiers = nil
102
- @pool = ConnectionPool.get_pool(@opts, &block)
103
-
104
- ::Sequel::DATABASES.push(self)
105
- end
106
-
107
- ### Class Methods ###
108
-
109
- # The Database subclass for the given adapter scheme.
110
- # Raises Sequel::AdapterNotFound if the adapter
111
- # could not be loaded.
112
- def self.adapter_class(scheme)
113
- scheme = scheme.to_s.gsub('-', '_').to_sym
114
-
115
- unless klass = ADAPTER_MAP[scheme]
116
- # attempt to load the adapter file
117
- begin
118
- Sequel.tsk_require "sequel/adapters/#{scheme}"
119
- rescue LoadError => e
120
- raise Sequel.convert_exception_class(e, AdapterNotFound)
121
- end
122
-
123
- # make sure we actually loaded the adapter
124
- unless klass = ADAPTER_MAP[scheme]
125
- raise AdapterNotFound, "Could not load #{scheme} adapter"
126
- end
127
- end
128
- klass
129
- end
130
-
131
- # Returns the scheme for the Database class.
132
- def self.adapter_scheme
133
- @scheme
134
- end
135
-
136
- # Connects to a database. See Sequel.connect.
137
- def self.connect(conn_string, opts = {})
138
- case conn_string
139
- when String
140
- if match = /\A(jdbc|do):/o.match(conn_string)
141
- c = adapter_class(match[1].to_sym)
142
- opts = {:uri=>conn_string}.merge(opts)
143
- else
144
- uri = URI.parse(conn_string)
145
- scheme = uri.scheme
146
- scheme = :dbi if scheme =~ /\Adbi-/
147
- c = adapter_class(scheme)
148
- uri_options = c.send(:uri_to_options, uri)
149
- uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
150
- uri_options.entries.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
151
- opts = uri_options.merge(opts)
152
- end
153
- when Hash
154
- opts = conn_string.merge(opts)
155
- c = adapter_class(opts[:adapter] || opts['adapter'])
156
- else
157
- raise Error, "Sequel::Database.connect takes either a Hash or a String, given: #{conn_string.inspect}"
158
- end
159
- # process opts a bit
160
- opts = opts.inject({}) do |m, kv| k, v = *kv
161
- k = :user if k.to_s == 'username'
162
- m[k.to_sym] = v
163
- m
164
- end
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?
171
- db.disconnect if db
172
- ::Sequel::DATABASES.delete(db)
173
- end
174
- end
175
- block_given? ? result : db
176
- end
177
-
178
- # The method to call on identifiers going into the database
179
- def self.identifier_input_method
180
- @@identifier_input_method
181
- end
182
-
183
- # Set the method to call on identifiers going into the database
184
- # See Sequel.identifier_input_method=.
185
- def self.identifier_input_method=(v)
186
- @@identifier_input_method = v || ""
187
- end
188
-
189
- # The method to call on identifiers coming from the database
190
- def self.identifier_output_method
191
- @@identifier_output_method
192
- end
193
-
194
- # Set the method to call on identifiers coming from the database
195
- # See Sequel.identifier_output_method=.
196
- def self.identifier_output_method=(v)
197
- @@identifier_output_method = v || ""
198
- end
199
-
200
- # Sets the default quote_identifiers mode for new databases.
201
- # See Sequel.quote_identifiers=.
202
- def self.quote_identifiers=(value)
203
- @@quote_identifiers = value
204
- end
205
-
206
- # Sets the default single_threaded mode for new databases.
207
- # See Sequel.single_threaded=.
208
- def self.single_threaded=(value)
209
- @@single_threaded = value
210
- end
211
-
212
- ### Private Class Methods ###
213
-
214
- # Sets the adapter scheme for the Database class. Call this method in
215
- # descendants of Database to allow connection using a URL. For example the
216
- # following:
217
- #
218
- # class Sequel::MyDB::Database < Sequel::Database
219
- # set_adapter_scheme :mydb
220
- # ...
221
- # end
222
- #
223
- # would allow connection using:
224
- #
225
- # Sequel.connect('mydb://user:password@dbserver/mydb')
226
- def self.set_adapter_scheme(scheme) # :nodoc:
227
- @scheme = scheme
228
- ADAPTER_MAP[scheme.to_sym] = self
229
- end
230
-
231
- # Converts a uri to an options hash. These options are then passed
232
- # to a newly created database object.
233
- def self.uri_to_options(uri) # :nodoc:
234
- { :user => uri.user,
235
- :password => uri.password,
236
- :host => uri.host,
237
- :port => uri.port,
238
- :database => (m = /\/(.*)/.match(uri.path)) && (m[1]) }
239
- end
240
-
241
- private_class_method :set_adapter_scheme, :uri_to_options
242
-
243
- ### Instance Methods ###
244
-
245
- # Runs the supplied SQL statement string on the database server.
246
- # Alias for run.
247
- def <<(sql)
248
- run(sql)
249
- end
250
-
251
- # Returns a dataset from the database. If the first argument is a string,
252
- # the method acts as an alias for Database#fetch, returning a dataset for
253
- # arbitrary SQL:
254
- #
255
- # DB['SELECT * FROM items WHERE name = ?', my_name].all
256
- #
257
- # Otherwise, acts as an alias for Database#from, setting the primary
258
- # table for the dataset:
259
- #
260
- # DB[:items].sql #=> "SELECT * FROM items"
261
- def [](*args)
262
- (String === args.first) ? fetch(*args) : from(*args)
263
- end
264
-
265
- # Dynamically add new servers or modify server options at runtime. Also adds new
266
- # servers to the connection pool. Intended for use with master/slave or shard
267
- # configurations where it is useful to add new server hosts at runtime.
268
- #
269
- # servers argument should be a hash with server name symbol keys and hash or
270
- # proc values. If a servers key is already in use, it's value is overridden
271
- # with the value provided.
272
- #
273
- # DB.add_servers(:f=>{:host=>"hash_host_f"})
274
- def add_servers(servers)
275
- @opts[:servers] = @opts[:servers] ? @opts[:servers].merge(servers) : servers
276
- @pool.add_servers(servers.keys)
277
- end
278
-
279
- # Call the prepared statement with the given name with the given hash
280
- # of arguments.
281
- def call(ps_name, hash={})
282
- prepared_statements[ps_name].call(hash)
283
- end
284
-
285
- # Cast the given type to a literal type
286
- def cast_type_literal(type)
287
- type_literal(:type=>type)
288
- end
289
-
290
- # Connects to the database. This method should be overridden by descendants.
291
- def connect(server)
292
- raise NotImplementedError, "#connect should be overridden by adapters"
293
- end
294
-
295
- # The database type for this database object, the same as the adapter scheme
296
- # by default. Should be overridden in adapters (especially shared adapters)
297
- # to be the correct type, so that even if two separate Database objects are
298
- # using different adapters you can tell that they are using the same database
299
- # type. Even better, you can tell that two Database objects that are using
300
- # the same adapter are connecting to different database types (think JDBC or
301
- # DataObjects).
302
- def database_type
303
- self.class.adapter_scheme
304
- end
305
-
306
- # Returns a blank dataset for this database
307
- def dataset
308
- ds = Sequel::Dataset.new(self)
309
- end
310
-
311
- # Disconnects all available connections from the connection pool. Any
312
- # connections currently in use will not be disconnected. Options:
313
- # * :servers - Should be a symbol specifing the server to disconnect from,
314
- # or an array of symbols to specify multiple servers.
315
- def disconnect(opts = {})
316
- pool.disconnect(opts)
317
- end
318
-
319
- # Yield a new database object for every server in the connection pool.
320
- # Intended for use in sharded environments where there is a need to make schema
321
- # modifications (DDL queries) on each shard.
322
- #
323
- # DB.each_server{|db| db.create_table(:users){primary_key :id; String :name}}
324
- def each_server(&block)
325
- servers.each{|s| self.class.connect(server_opts(s), &block)}
326
- end
327
-
328
- # Executes the given SQL on the database. This method should be overridden in descendants.
329
- # This method should not be called directly by user code.
330
- def execute(sql, opts={})
331
- raise NotImplementedError, "#execute should be overridden by adapters"
332
- end
333
-
334
- # Method that should be used when submitting any DDL (Data Definition
335
- # Language) SQL. By default, calls execute_dui.
336
- # This method should not be called directly by user code.
337
- def execute_ddl(sql, opts={}, &block)
338
- execute_dui(sql, opts, &block)
339
- end
340
-
341
- # Method that should be used when issuing a DELETE, UPDATE, or INSERT
342
- # statement. By default, calls execute.
343
- # This method should not be called directly by user code.
344
- def execute_dui(sql, opts={}, &block)
345
- execute(sql, opts, &block)
346
- end
347
-
348
- # Method that should be used when issuing a INSERT
349
- # statement. By default, calls execute_dui.
350
- # This method should not be called directly by user code.
351
- def execute_insert(sql, opts={}, &block)
352
- execute_dui(sql, opts, &block)
353
- end
354
-
355
- # Fetches records for an arbitrary SQL statement. If a block is given,
356
- # it is used to iterate over the records:
357
- #
358
- # DB.fetch('SELECT * FROM items'){|r| p r}
359
- #
360
- # The method returns a dataset instance:
361
- #
362
- # DB.fetch('SELECT * FROM items').all
363
- #
364
- # Fetch can also perform parameterized queries for protection against SQL
365
- # injection:
366
- #
367
- # DB.fetch('SELECT * FROM items WHERE name = ?', my_name).all
368
- def fetch(sql, *args, &block)
369
- ds = dataset.with_sql(sql, *args)
370
- ds.each(&block) if block
371
- ds
372
- end
373
-
374
- # Returns a new dataset with the from method invoked. If a block is given,
375
- # it is used as a filter on the dataset.
376
- def from(*args, &block)
377
- ds = dataset.from(*args)
378
- block ? ds.filter(&block) : ds
379
- end
380
-
381
- # Returns a single value from the database, e.g.:
382
- #
383
- # # SELECT 1
384
- # DB.get(1) #=> 1
385
- #
386
- # # SELECT version()
387
- # DB.get(:version.sql_function) #=> ...
388
- def get(*args, &block)
389
- dataset.get(*args, &block)
390
- end
391
-
392
- # The method to call on identifiers going into the database
393
- def identifier_input_method
394
- case @identifier_input_method
395
- when nil
396
- @identifier_input_method = @opts.fetch(:identifier_input_method, (@@identifier_input_method.nil? ? identifier_input_method_default : @@identifier_input_method))
397
- @identifier_input_method == "" ? nil : @identifier_input_method
398
- when ""
399
- nil
400
- else
401
- @identifier_input_method
402
- end
403
- end
404
-
405
- # Set the method to call on identifiers going into the database
406
- def identifier_input_method=(v)
407
- reset_schema_utility_dataset
408
- @identifier_input_method = v || ""
409
- end
410
-
411
- # The method to call on identifiers coming from the database
412
- def identifier_output_method
413
- case @identifier_output_method
414
- when nil
415
- @identifier_output_method = @opts.fetch(:identifier_output_method, (@@identifier_output_method.nil? ? identifier_output_method_default : @@identifier_output_method))
416
- @identifier_output_method == "" ? nil : @identifier_output_method
417
- when ""
418
- nil
419
- else
420
- @identifier_output_method
421
- end
422
- end
423
-
424
- # Set the method to call on identifiers coming from the database
425
- def identifier_output_method=(v)
426
- reset_schema_utility_dataset
427
- @identifier_output_method = v || ""
428
- end
429
-
430
- # Returns a string representation of the database object including the
431
- # class name and the connection URI (or the opts if the URI
432
- # cannot be constructed).
433
- def inspect
434
- "#<#{self.class}: #{(uri rescue opts).inspect}>"
435
- end
436
-
437
- # Proxy the literal call to the dataset, used for default values.
438
- def literal(v)
439
- schema_utility_dataset.literal(v)
440
- end
441
-
442
- # Log a message at level info to all loggers.
443
- def log_info(message, args=nil)
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
461
- end
462
-
463
- # Remove any existing loggers and just use the given logger.
464
- def logger=(logger)
465
- @loggers = Array(logger)
466
- end
467
-
468
- # Whether to quote identifiers (columns and tables) for this database
469
- def quote_identifiers=(v)
470
- reset_schema_utility_dataset
471
- @quote_identifiers = v
472
- end
473
-
474
- # Returns true if the database quotes identifiers.
475
- def quote_identifiers?
476
- return @quote_identifiers unless @quote_identifiers.nil?
477
- @quote_identifiers = @opts.fetch(:quote_identifiers, (@@quote_identifiers.nil? ? quote_identifiers_default : @@quote_identifiers))
478
- end
479
-
480
- # Dynamically remove existing servers from the connection pool. Intended for
481
- # use with master/slave or shard configurations where it is useful to remove
482
- # existing server hosts at runtime.
483
- #
484
- # servers should be symbols or arrays of symbols. If a nonexistent server
485
- # is specified, it is ignored. If no servers have been specified for
486
- # this database, no changes are made. If you attempt to remove the :default server,
487
- # an error will be raised.
488
- #
489
- # DB.remove_servers(:f1, :f2)
490
- def remove_servers(*servers)
491
- if @opts[:servers] && !@opts[:servers].empty?
492
- servs = @opts[:servers].dup
493
- servers.flatten!
494
- servers.each{|s| servs.delete(s)}
495
- @opts[:servers] = servs
496
- @pool.remove_servers(servers)
497
- end
498
- end
499
-
500
- # Runs the supplied SQL statement string on the database server. Returns nil.
501
- # Options:
502
- # * :server - The server to run the SQL on.
503
- def run(sql, opts={})
504
- execute_ddl(sql, opts)
505
- nil
506
- end
507
-
508
- # Parse the schema from the database.
509
- # Returns the schema for the given table as an array with all members being arrays of length 2,
510
- # the first member being the column name, and the second member being a hash of column information.
511
- # Available options are:
512
- #
513
- # * :reload - Get fresh information from the database, instead of using
514
- # cached information. If table_name is blank, :reload should be used
515
- # unless you are sure that schema has not been called before with a
516
- # table_name, otherwise you may only getting the schemas for tables
517
- # that have been requested explicitly.
518
- # * :schema - An explicit schema to use. It may also be implicitly provided
519
- # via the table name.
520
- def schema(table, opts={})
521
- raise(Error, 'schema parsing is not implemented on this database') unless respond_to?(:schema_parse_table, true)
522
-
523
- sch, table_name = schema_and_table(table)
524
- quoted_name = quote_schema_table(table)
525
- opts = opts.merge(:schema=>sch) if sch && !opts.include?(:schema)
526
-
527
- @schemas.delete(quoted_name) if opts[:reload]
528
- return @schemas[quoted_name] if @schemas[quoted_name]
529
-
530
- cols = schema_parse_table(table_name, opts)
531
- raise(Error, 'schema parsing returned no columns, table probably doesn\'t exist') if cols.nil? || cols.empty?
532
- cols.each{|_,c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
533
- @schemas[quoted_name] = cols
534
- end
535
-
536
- # Returns a new dataset with the select method invoked.
537
- def select(*args, &block)
538
- dataset.select(*args, &block)
539
- end
540
-
541
- # An array of servers/shards for this Database object.
542
- def servers
543
- pool.servers
544
- end
545
-
546
- # Returns true if the database is using a single-threaded connection pool.
547
- def single_threaded?
548
- @single_threaded
549
- end
550
-
551
- # Acquires a database connection, yielding it to the passed block.
552
- def synchronize(server=nil, &block)
553
- @pool.hold(server || :default, &block)
554
- end
555
-
556
- # Whether the database and adapter support savepoints, false by default
557
- def supports_savepoints?
558
- false
559
- end
560
-
561
- # Returns true if a table with the given name exists. This requires a query
562
- # to the database.
563
- def table_exists?(name)
564
- begin
565
- from(name).first
566
- true
567
- rescue
568
- false
569
- end
570
- end
571
-
572
- # Attempts to acquire a database connection. Returns true if successful.
573
- # Will probably raise an error if unsuccessful.
574
- def test_connection(server=nil)
575
- synchronize(server){|conn|}
576
- true
577
- end
578
-
579
- # Starts a database transaction. When a database transaction is used,
580
- # either all statements are successful or none of the statements are
581
- # successful. Note that MySQL MyISAM tabels do not support transactions.
582
- #
583
- # The following options are respected:
584
- #
585
- # * :server - The server to use for the transaction
586
- # * :savepoint - Whether to create a new savepoint for this transaction,
587
- # only respected if the database adapter supports savepoints. By
588
- # default Sequel will reuse an existing transaction, so if you want to
589
- # use a savepoint you must use this option.
590
- def transaction(opts={}, &block)
591
- synchronize(opts[:server]) do |conn|
592
- return yield(conn) if already_in_transaction?(conn, opts)
593
- _transaction(conn, &block)
594
- end
595
- end
596
-
597
- # Typecast the value to the given column_type. Calls
598
- # typecast_value_#{column_type} if the method exists,
599
- # otherwise returns the value.
600
- # This method should raise Sequel::InvalidValue if assigned value
601
- # is invalid.
602
- def typecast_value(column_type, value)
603
- return nil if value.nil?
604
- meth = "typecast_value_#{column_type}"
605
- begin
606
- respond_to?(meth, true) ? send(meth, value) : value
607
- rescue ArgumentError, TypeError => e
608
- raise Sequel.convert_exception_class(e, InvalidValue)
609
- end
610
- end
611
-
612
- # Returns the URI identifying the database.
613
- # This method can raise an error if the database used options
614
- # instead of a connection string.
615
- def uri
616
- uri = URI::Generic.new(
617
- self.class.adapter_scheme.to_s,
618
- nil,
619
- @opts[:host],
620
- @opts[:port],
621
- nil,
622
- "/#{@opts[:database]}",
623
- nil,
624
- nil,
625
- nil
626
- )
627
- uri.user = @opts[:user]
628
- uri.password = @opts[:password] if uri.user
629
- uri.to_s
630
- end
631
-
632
- # Explicit alias of uri for easier subclassing.
633
- def url
634
- uri
635
- end
636
-
637
- private
638
-
639
- # Internal generic transaction method. Any exception raised by the given
640
- # block will cause the transaction to be rolled back. If the exception is
641
- # not Sequel::Rollback, the error will be reraised. If no exception occurs
642
- # inside the block, the transaction is commited.
643
- def _transaction(conn)
644
- begin
645
- add_transaction
646
- t = begin_transaction(conn)
647
- yield(conn)
648
- rescue Exception => e
649
- rollback_transaction(t) if t
650
- transaction_error(e)
651
- ensure
652
- begin
653
- commit_transaction(t) unless e
654
- rescue Exception => e
655
- raise_error(e, :classes=>database_error_classes)
656
- ensure
657
- remove_transaction(t)
658
- end
659
- end
660
- end
661
-
662
- # Add the current thread to the list of active transactions
663
- def add_transaction
664
- th = Thread.current
665
- if supports_savepoints?
666
- unless @transactions.include?(th)
667
- th[:sequel_transaction_depth] = 0
668
- @transactions << th
669
- end
670
- else
671
- @transactions << th
672
- end
673
- end
674
-
675
- # Whether the current thread/connection is already inside a transaction
676
- def already_in_transaction?(conn, opts)
677
- @transactions.include?(Thread.current) && (!supports_savepoints? || !opts[:savepoint])
678
- end
679
-
680
- # SQL to start a new savepoint
681
- def begin_savepoint_sql(depth)
682
- SQL_SAVEPOINT % depth
683
- end
684
-
685
- # Start a new database transaction on the given connection.
686
- def begin_transaction(conn)
687
- if supports_savepoints?
688
- th = Thread.current
689
- depth = th[:sequel_transaction_depth]
690
- conn = transaction_statement_object(conn) if respond_to?(:transaction_statement_object, true)
691
- log_connection_execute(conn, depth > 0 ? begin_savepoint_sql(depth) : begin_transaction_sql)
692
- th[:sequel_transaction_depth] += 1
693
- else
694
- log_connection_execute(conn, begin_transaction_sql)
695
- end
696
- conn
697
- end
698
-
699
- # SQL to BEGIN a transaction.
700
- def begin_transaction_sql
701
- SQL_BEGIN
702
- end
703
-
704
- # Returns true when the object is considered blank.
705
- # The only objects that are blank are nil, false,
706
- # strings with all whitespace, and ones that respond
707
- # true to empty?
708
- def blank_object?(obj)
709
- return obj.blank? if obj.respond_to?(:blank?)
710
- case obj
711
- when NilClass, FalseClass
712
- true
713
- when Numeric, TrueClass
714
- false
715
- when String
716
- obj.strip.empty?
717
- else
718
- obj.respond_to?(:empty?) ? obj.empty? : false
719
- end
720
- end
721
-
722
- # Convert the given default, which should be a database specific string, into
723
- # a ruby object.
724
- def column_schema_to_ruby_default(default, type)
725
- return if default.nil?
726
- orig_default = default
727
- if database_type == :postgres and m = POSTGRES_DEFAULT_RE.match(default)
728
- default = m[1] || m[2]
729
- end
730
- if database_type == :mssql and m = MSSQL_DEFAULT_RE.match(default)
731
- default = m[1] || m[2]
732
- end
733
- if [:string, :blob, :date, :datetime, :time, :enum].include?(type)
734
- if database_type == :mysql
735
- return if [:date, :datetime, :time].include?(type) && MYSQL_TIMESTAMP_RE.match(default)
736
- orig_default = default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
737
- end
738
- return unless m = STRING_DEFAULT_RE.match(default)
739
- default = m[1].gsub("''", "'")
740
- end
741
- res = begin
742
- case type
743
- when :boolean
744
- case default
745
- when /[f0]/i
746
- false
747
- when /[t1]/i
748
- true
749
- end
750
- when :string, :enum
751
- default
752
- when :blob
753
- Sequel::SQL::Blob.new(default)
754
- when :integer
755
- Integer(default)
756
- when :float
757
- Float(default)
758
- when :date
759
- Sequel.string_to_date(default)
760
- when :datetime
761
- DateTime.parse(default)
762
- when :time
763
- Sequel.string_to_time(default)
764
- when :decimal
765
- BigDecimal.new(default)
766
- end
767
- rescue
768
- nil
769
- end
770
- end
771
-
772
- # SQL to commit a savepoint
773
- def commit_savepoint_sql(depth)
774
- SQL_RELEASE_SAVEPOINT % depth
775
- end
776
-
777
- # Commit the active transaction on the connection
778
- def commit_transaction(conn)
779
- if supports_savepoints?
780
- depth = Thread.current[:sequel_transaction_depth]
781
- log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
782
- else
783
- log_connection_execute(conn, commit_transaction_sql)
784
- end
785
- end
786
-
787
- # SQL to COMMIT a transaction.
788
- def commit_transaction_sql
789
- SQL_COMMIT
790
- end
791
-
792
- # Method called on the connection object to execute SQL on the database,
793
- # used by the transaction code.
794
- def connection_execute_method
795
- :execute
796
- end
797
-
798
- # The default options for the connection pool.
799
- def connection_pool_default_options
800
- {}
801
- end
802
-
803
- # Which transaction errors to translate, blank by default.
804
- def database_error_classes
805
- []
806
- end
807
-
808
- # The default value for default_schema.
809
- def default_schema_default
810
- nil
811
- end
812
-
813
- # The method to apply to identifiers going into the database by default.
814
- # Should be overridden in subclasses for databases that fold unquoted
815
- # identifiers to lower case instead of uppercase, such as
816
- # MySQL, PostgreSQL, and SQLite.
817
- def identifier_input_method_default
818
- :upcase
819
- end
820
-
821
- # The method to apply to identifiers coming the database by default.
822
- # Should be overridden in subclasses for databases that fold unquoted
823
- # identifiers to lower case instead of uppercase, such as
824
- # MySQL, PostgreSQL, and SQLite.
825
- def identifier_output_method_default
826
- :downcase
827
- end
828
-
829
- # Return a Method object for the dataset's output_identifier_method.
830
- # Used in metadata parsing to make sure the returned information is in the
831
- # correct format.
832
- def input_identifier_meth
833
- dataset.method(:input_identifier)
834
- end
835
-
836
- # Log the given SQL and then execute it on the connection, used by
837
- # the transaction code.
838
- def log_connection_execute(conn, 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)}
852
- end
853
-
854
- # Return a dataset that uses the default identifier input and output methods
855
- # for this database. Used when parsing metadata so that column symbols are
856
- # returned as expected.
857
- def metadata_dataset
858
- return @metadata_dataset if @metadata_dataset
859
- ds = dataset
860
- ds.identifier_input_method = identifier_input_method_default
861
- ds.identifier_output_method = identifier_output_method_default
862
- @metadata_dataset = ds
863
- end
864
-
865
- # Return a Method object for the dataset's output_identifier_method.
866
- # Used in metadata parsing to make sure the returned information is in the
867
- # correct format.
868
- def output_identifier_meth
869
- dataset.method(:output_identifier)
870
- end
871
-
872
- # Whether to quote identifiers by default for this database, true
873
- # by default.
874
- def quote_identifiers_default
875
- true
876
- end
877
-
878
- # SQL to ROLLBACK a transaction.
879
- def rollback_transaction_sql
880
- SQL_ROLLBACK
881
- end
882
-
883
- # Convert the given exception to a DatabaseError, keeping message
884
- # and traceback.
885
- def raise_error(exception, opts={})
886
- if !opts[:classes] || Array(opts[:classes]).any?{|c| exception.is_a?(c)}
887
- raise Sequel.convert_exception_class(exception, opts[:disconnect] ? DatabaseDisconnectError : DatabaseError)
888
- else
889
- raise exception
890
- end
891
- end
892
-
893
- # Remove the cached schema for the given schema name
894
- def remove_cached_schema(table)
895
- @schemas.delete(quote_schema_table(table)) if @schemas
896
- end
897
-
898
- # Remove the current thread from the list of active transactions
899
- def remove_transaction(conn)
900
- th = Thread.current
901
- @transactions.delete(th) if !supports_savepoints? || ((th[:sequel_transaction_depth] -= 1) <= 0)
902
- end
903
-
904
- # Remove the cached schema_utility_dataset, because the identifier
905
- # quoting has changed.
906
- def reset_schema_utility_dataset
907
- @schema_utility_dataset = nil
908
- end
909
-
910
- # SQL to rollback to a savepoint
911
- def rollback_savepoint_sql(depth)
912
- SQL_ROLLBACK_TO_SAVEPOINT % depth
913
- end
914
-
915
- # Rollback the active transaction on the connection
916
- def rollback_transaction(conn)
917
- if supports_savepoints?
918
- depth = Thread.current[:sequel_transaction_depth]
919
- log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
920
- else
921
- log_connection_execute(conn, rollback_transaction_sql)
922
- end
923
- end
924
-
925
- # Split the schema information from the table
926
- def schema_and_table(table_name)
927
- schema_utility_dataset.schema_and_table(table_name)
928
- end
929
-
930
- # Return true if the given column schema represents an autoincrementing primary key.
931
- def schema_autoincrementing_primary_key?(schema)
932
- !!schema[:primary_key]
933
- end
934
-
935
- # Match the database's column type to a ruby type via a
936
- # regular expression. The following ruby types are supported:
937
- # integer, string, date, datetime, boolean, and float.
938
- def schema_column_type(db_type)
939
- case db_type
940
- when /\Ainterval\z/io
941
- :interval
942
- when /\A(character( varying)?|n?(var)?char|n?text)/io
943
- :string
944
- when /\A(int(eger)?|(big|small|tiny)int)/io
945
- :integer
946
- when /\Adate\z/io
947
- :date
948
- when /\A((small)?datetime|timestamp( with(out)? time zone)?)\z/io
949
- :datetime
950
- when /\Atime( with(out)? time zone)?\z/io
951
- :time
952
- when /\A(boolean|bit)\z/io
953
- :boolean
954
- when /\A(real|float|double( precision)?)\z/io
955
- :float
956
- when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
957
- $1 && $1 == '0' ? :integer : :decimal
958
- when /bytea|blob|image|(var)?binary/io
959
- :blob
960
- when /\Aenum/io
961
- :enum
962
- end
963
- end
964
-
965
- # The dataset to use for proxying certain schema methods.
966
- def schema_utility_dataset
967
- @schema_utility_dataset ||= dataset
968
- end
969
-
970
- # Return the options for the given server by merging the generic
971
- # options for all server with the specific options for the given
972
- # server specified in the :servers option.
973
- def server_opts(server)
974
- opts = if @opts[:servers] && server_options = @opts[:servers][server]
975
- case server_options
976
- when Hash
977
- @opts.merge(server_options)
978
- when Proc
979
- @opts.merge(server_options.call(self))
980
- else
981
- raise Error, 'Server opts should be a hash or proc'
982
- end
983
- else
984
- @opts.dup
985
- end
986
- opts.delete(:servers)
987
- opts
988
- end
989
-
990
- # Raise a database error unless the exception is an Rollback.
991
- def transaction_error(e)
992
- raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
993
- end
994
-
995
- # Typecast the value to an SQL::Blob
996
- def typecast_value_blob(value)
997
- value.is_a?(Sequel::SQL::Blob) ? value : Sequel::SQL::Blob.new(value)
998
- end
999
-
1000
- # Typecast the value to true, false, or nil
1001
- def typecast_value_boolean(value)
1002
- case value
1003
- when false, 0, "0", /\Af(alse)?\z/i
1004
- false
1005
- else
1006
- blank_object?(value) ? nil : true
1007
- end
1008
- end
1009
-
1010
- # Typecast the value to a Date
1011
- def typecast_value_date(value)
1012
- case value
1013
- when Date
1014
- value
1015
- when DateTime, Time
1016
- Date.new(value.year, value.month, value.day)
1017
- when String
1018
- Sequel.string_to_date(value)
1019
- when Hash
1020
- Date.new(*[:year, :month, :day].map{|x| (value[x] || value[x.to_s]).to_i})
1021
- else
1022
- raise InvalidValue, "invalid value for Date: #{value.inspect}"
1023
- end
1024
- end
1025
-
1026
- # Typecast the value to a DateTime or Time depending on Sequel.datetime_class
1027
- def typecast_value_datetime(value)
1028
- raise(Sequel::InvalidValue, "invalid value for Datetime: #{value.inspect}") unless [DateTime, Date, Time, String, Hash].any?{|c| value.is_a?(c)}
1029
- klass = Sequel.datetime_class
1030
- if value.is_a?(Hash)
1031
- klass.send(klass == Time ? :mktime : :new, *[:year, :month, :day, :hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
1032
- else
1033
- Sequel.typecast_to_application_timestamp(value)
1034
- end
1035
- end
1036
-
1037
- # Typecast the value to a BigDecimal
1038
- def typecast_value_decimal(value)
1039
- case value
1040
- when BigDecimal
1041
- value
1042
- when String, Numeric
1043
- BigDecimal.new(value.to_s)
1044
- else
1045
- raise InvalidValue, "invalid value for BigDecimal: #{value.inspect}"
1046
- end
1047
- end
1048
-
1049
- # Typecast the value to a Float
1050
- def typecast_value_float(value)
1051
- Float(value)
1052
- end
1053
-
1054
- # Typecast the value to an Integer
1055
- def typecast_value_integer(value)
1056
- Integer(value)
1057
- end
1058
-
1059
- # Typecast the value to a String
1060
- def typecast_value_string(value)
1061
- value.to_s
1062
- end
1063
-
1064
- # Typecast the value to a Time
1065
- def typecast_value_time(value)
1066
- case value
1067
- when Time
1068
- value
1069
- when String
1070
- Sequel.string_to_time(value)
1071
- when Hash
1072
- t = Time.now
1073
- Time.mktime(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
1074
- else
1075
- raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
1076
- end
1077
- end
1078
18
  end
19
+
20
+ require(%w"connecting dataset dataset_defaults logging misc query schema_generator schema_methods", 'database')
1079
21
  end