sequel 3.11.0 → 3.12.0

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