sequel 3.33.0 → 3.34.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 (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. metadata +35 -7
@@ -229,13 +229,19 @@ module Sequel
229
229
  end
230
230
  end
231
231
  unless cps
232
- cps = log_yield("Preparing #{name}: #{sql}"){conn.prepare(sql)}
232
+ cps = log_yield("PREPARE #{name}: #{sql}"){conn.prepare(sql)}
233
233
  conn.prepared_statements[name] = [cps, sql]
234
234
  end
235
+ log_sql = "EXECUTE #{name}"
236
+ if ps.log_sql
237
+ log_sql << " ("
238
+ log_sql << sql
239
+ log_sql << ")"
240
+ end
235
241
  if block
236
- log_yield("Executing prepared statement #{name}", args){cps.execute(ps_args, &block)}
242
+ log_yield(log_sql, args){cps.execute(ps_args, &block)}
237
243
  else
238
- log_yield("Executing prepared statement #{name}", args){cps.execute!(ps_args){|r|}}
244
+ log_yield(log_sql, args){cps.execute!(ps_args){|r|}}
239
245
  case type
240
246
  when :insert
241
247
  conn.last_insert_row_id
@@ -136,10 +136,10 @@ module Sequel
136
136
  Database::DatasetClass = self
137
137
 
138
138
  # Set the columns and yield the hashes to the block.
139
- def fetch_rows(sql, &block)
139
+ def fetch_rows(sql)
140
140
  execute(sql) do |res|
141
141
  @columns = res.fields
142
- res.each(&block)
142
+ res.each{|h| yield h}
143
143
  end
144
144
  self
145
145
  end
@@ -36,11 +36,6 @@ module Sequel
36
36
  include Sequel::MySQL::DatasetMethods
37
37
  APOS = Dataset::APOS
38
38
 
39
- # Use execute_insert to execute the replace_sql.
40
- def replace(*args)
41
- execute_insert(replace_sql(*args))
42
- end
43
-
44
39
  private
45
40
 
46
41
  # Use Swift's escape method for quoting.
@@ -85,6 +85,16 @@ module Sequel
85
85
  def log_connection_execute(conn, sql)
86
86
  conn.execute(sql)
87
87
  end
88
+
89
+ # Remove all other options except for ones specifically handled, as
90
+ # otherwise swift passes them to dbic++ which passes them to PostgreSQL
91
+ # which can raise an error.
92
+ def server_opts(o)
93
+ o = super
94
+ so = {}
95
+ [:db, :user, :password, :host, :port].each{|s| so[s] = o[s] if o.has_key?(s)}
96
+ so
97
+ end
88
98
 
89
99
  # Extend the adapter with the Swift PostgreSQL AdapterMethods.
90
100
  def setup_connection(conn)
@@ -66,6 +66,8 @@ module Sequel
66
66
  SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.table), v(o.table_alias))
67
67
  when SQL::JoinClause
68
68
  SQL::JoinClause.new(o.join_type, v(o.table), v(o.table_alias))
69
+ when SQL::Wrapper
70
+ SQL::Wrapper.new(v(o.value))
69
71
  else
70
72
  o
71
73
  end
@@ -172,6 +174,8 @@ module Sequel
172
174
  def v(o)
173
175
  if o.is_a?(SQL::ComplexExpression) && UNBIND_OPS.include?(o.op)
174
176
  l, r = o.args
177
+ l = l.value if l.is_a?(Sequel::SQL::Wrapper)
178
+ r = r.value if r.is_a?(Sequel::SQL::Wrapper)
175
179
  if UNBIND_KEY_CLASSES.any?{|c| l.is_a?(c)} && UNBIND_VALUE_CLASSES.any?{|c| r.is_a?(c)} && !r.is_a?(LiteralString)
176
180
  key = bind_key(l)
177
181
  if (old = binds[key]) && old != r
@@ -57,6 +57,14 @@ class Sequel::ConnectionPool
57
57
  end
58
58
  end
59
59
  extend ClassMethods
60
+
61
+ # The after_connect proc used for this pool. This is called with each new
62
+ # connection made, and is usually used to set custom per-connection settings.
63
+ attr_accessor :after_connect
64
+
65
+ # The disconnect_proc used for the pool. This is called with each connection
66
+ # that is disconnected, usually to clean up related resources.
67
+ attr_accessor :disconnection_proc
60
68
 
61
69
  # Instantiates a connection pool with the given options. The block is called
62
70
  # with a single symbol (specifying the server/shard to use) every time a new
@@ -26,6 +26,11 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
26
26
  def add_servers(servers)
27
27
  servers.each{|s| @servers[s] = s}
28
28
  end
29
+
30
+ # Yield all of the currently established connections
31
+ def all_connections
32
+ @conns.values.each{|c| yield c}
33
+ end
29
34
 
30
35
  # The connection for the given server.
31
36
  def conn(server=:default)
@@ -45,6 +45,23 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
45
45
  @allocated[server]
46
46
  end
47
47
 
48
+ # Yield all of the available connections, and the ones currently allocated to
49
+ # this thread. This will not yield connections currently allocated to other
50
+ # threads, as it is not safe to operate on them. This holds the mutex while
51
+ # it is yielding all of the connections, which means that until
52
+ # the method's block returns, the pool is locked.
53
+ def all_connections
54
+ t = Thread.current
55
+ sync do
56
+ @allocated.values.each do |threads|
57
+ threads.each do |thread, conn|
58
+ yield conn if t == thread
59
+ end
60
+ end
61
+ @available_connections.values.each{|v| v.each{|c| yield c}}
62
+ end
63
+ end
64
+
48
65
  # An array of connections opened but not currently used, for the given
49
66
  # server. Nonexistent servers will return nil. Treat this as read only, do
50
67
  # not modify the resulting object.
@@ -8,6 +8,11 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
8
8
  @conn ? 1 : 0
9
9
  end
10
10
 
11
+ # Yield the connection if one has been made.
12
+ def all_connections
13
+ yield @conn if @conn
14
+ end
15
+
11
16
  # Disconnect the connection from the database.
12
17
  def disconnect(opts=nil, &block)
13
18
  return unless @conn
@@ -36,6 +36,20 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
36
36
  @allocated.length + @available_connections.length
37
37
  end
38
38
 
39
+ # Yield all of the available connections, and the one currently allocated to
40
+ # this thread. This will not yield connections currently allocated to other
41
+ # threads, as it is not safe to operate on them. This holds the mutex while
42
+ # it is yielding all of the available connections, which means that until
43
+ # the method's block returns, the pool is locked.
44
+ def all_connections
45
+ hold do |c|
46
+ sync do
47
+ yield c
48
+ @available_connections.each{|c| yield c}
49
+ end
50
+ end
51
+ end
52
+
39
53
  # Removes all connections currently available, optionally
40
54
  # yielding each connection to the given block. This method has the effect of
41
55
  # disconnecting from the database, assuming that no connections are currently
data/lib/sequel/core.rb CHANGED
@@ -1,3 +1,4 @@
1
+ warn 'Sequel support for ruby <1.8.7 is deprecated and will be removed in 3.35.0' if RUBY_VERSION < '1.8.7'
1
2
  %w'bigdecimal date thread time uri'.each{|f| require f}
2
3
 
3
4
  # Top level module for Sequel
@@ -17,8 +18,10 @@
17
18
  #
18
19
  # Sequel.sqlite('blog.db'){|db| puts db[:users].count}
19
20
  #
20
- # You can set the +SEQUEL_NO_CORE_EXTENSIONS+ constant or environment variable to have
21
- # Sequel not extend the core classes.
21
+ # Sequel currently adds methods to the Array, Hash, String and Symbol classes by
22
+ # default. You can either require 'sequel/no_core_ext' or set the
23
+ # +SEQUEL_NO_CORE_EXTENSIONS+ constant or environment variable before requiring
24
+ # sequel to have # Sequel not add methods to those classes.
22
25
  #
23
26
  # For a more expanded introduction, see the {README}[link:files/README_rdoc.html].
24
27
  # For a quicker introduction, see the {cheat sheet}[link:files/doc/cheat_sheet_rdoc.html].
@@ -140,6 +143,24 @@ module Sequel
140
143
  def self.connect(*args, &block)
141
144
  Database.connect(*args, &block)
142
145
  end
146
+
147
+ if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
148
+ # Whether the core extensions are enabled. The core extensions are enabled by
149
+ # default for backwards compatibility, but can be disabled using the SEQUEL_NO_CORE_EXTENSIONS
150
+ # constant or environment variable.
151
+ def self.core_extensions?
152
+ # We override this method to return true inside the core_extensions.rb file,
153
+ # but we also set it here because that file is not loaded until most of Sequel
154
+ # is finished loading, and parts of Sequel check whether the core extensions
155
+ # are loaded.
156
+ true
157
+ end
158
+ else
159
+ def self.core_extensions?
160
+ false
161
+ end
162
+ end
163
+
143
164
 
144
165
  # Convert the +exception+ to the given class. The given class should be
145
166
  # <tt>Sequel::Error</tt> or a subclass. Returns an instance of +klass+ with
@@ -356,7 +377,7 @@ module Sequel
356
377
  private_class_method :adapter_method, :def_adapter_method
357
378
 
358
379
  require(%w"metaprogramming sql connection_pool exceptions dataset database timezones ast_transformer version")
359
- require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
380
+ extension(:core_extensions) if Sequel.core_extensions?
360
381
 
361
382
  # Add the database adapter class methods to Sequel via metaprogramming
362
383
  def_adapter_method(*Database::ADAPTERS)
@@ -46,6 +46,7 @@ module Sequel
46
46
  when String
47
47
  if match = /\A(jdbc|do):/o.match(conn_string)
48
48
  c = adapter_class(match[1].to_sym)
49
+ opts = opts.merge(:orig_opts=>opts.dup)
49
50
  opts = {:uri=>conn_string}.merge(opts)
50
51
  else
51
52
  uri = URI.parse(conn_string)
@@ -55,11 +56,14 @@ module Sequel
55
56
  uri_options = c.send(:uri_to_options, uri)
56
57
  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?
57
58
  uri_options.to_a.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
59
+ opts = opts.merge(:orig_opts=>opts.dup)
60
+ opts[:uri] = conn_string
58
61
  opts = uri_options.merge(opts)
59
62
  opts[:adapter] = scheme
60
63
  end
61
64
  when Hash
62
65
  opts = conn_string.merge(opts)
66
+ opts = opts.merge(:orig_opts=>opts.dup)
63
67
  c = adapter_class(opts[:adapter_class] || opts[:adapter] || opts['adapter'])
64
68
  else
65
69
  raise Error, "Sequel::Database.connect takes either a Hash or a String, given: #{conn_string.inspect}"
@@ -209,20 +213,26 @@ module Sequel
209
213
  @single_threaded
210
214
  end
211
215
 
212
- # Acquires a database connection, yielding it to the passed block. This is
213
- # useful if you want to make sure the same connection is used for all
214
- # database queries in the block. It is also useful if you want to gain
215
- # direct access to the underlying connection object if you need to do
216
- # something Sequel does not natively support.
217
- #
218
- # If a server option is given, acquires a connection for that specific
219
- # server, instead of the :default server.
220
- #
221
- # DB.synchronize do |conn|
222
- # ...
223
- # end
224
- def synchronize(server=nil, &block)
225
- @pool.hold(server || :default, &block)
216
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
217
+ # Acquires a database connection, yielding it to the passed block. This is
218
+ # useful if you want to make sure the same connection is used for all
219
+ # database queries in the block. It is also useful if you want to gain
220
+ # direct access to the underlying connection object if you need to do
221
+ # something Sequel does not natively support.
222
+ #
223
+ # If a server option is given, acquires a connection for that specific
224
+ # server, instead of the :default server.
225
+ #
226
+ # DB.synchronize do |conn|
227
+ # ...
228
+ # end
229
+ def synchronize(server=nil)
230
+ @pool.hold(server || :default){|conn| yield conn}
231
+ end
232
+ else
233
+ def synchronize(server=nil, &block)
234
+ @pool.hold(server || :default, &block)
235
+ end
226
236
  end
227
237
 
228
238
  # Attempts to acquire a database connection. Returns true if successful.
@@ -88,6 +88,7 @@ module Sequel
88
88
  # end
89
89
  def extend_datasets(mod=nil, &block)
90
90
  raise(Error, "must provide either mod or block, not both") if mod && block
91
+ reset_schema_utility_dataset
91
92
  mod = Module.new(&block) if block
92
93
  if @dataset_modules.empty?
93
94
  @dataset_modules = [mod]
@@ -121,10 +121,14 @@ module Sequel
121
121
  end
122
122
 
123
123
  # Returns a string representation of the database object including the
124
- # class name and the connection URI (or the opts if the URI
125
- # cannot be constructed).
124
+ # class name and connection URI and options used when connecting (if any).
126
125
  def inspect
127
- "#<#{self.class}: #{(uri rescue opts).inspect}>"
126
+ a = []
127
+ a << uri.inspect if uri
128
+ if (oo = opts[:orig_opts]) && !oo.empty?
129
+ a << oo.inspect
130
+ end
131
+ "#<#{self.class}: #{a.join(' ')}>"
128
132
  end
129
133
 
130
134
  # Proxy the literal call to the dataset.
@@ -148,6 +152,12 @@ module Sequel
148
152
  false
149
153
  end
150
154
 
155
+ # Whether the database supports DROP TABLE IF EXISTS syntax,
156
+ # default is the same as #supports_create_table_if_not_exists?.
157
+ def supports_drop_table_if_exists?
158
+ supports_create_table_if_not_exists?
159
+ end
160
+
151
161
  # Whether the database and adapter support prepared transactions
152
162
  # (two-phase commit), false by default.
153
163
  def supports_prepared_transactions?
@@ -197,29 +207,10 @@ module Sequel
197
207
  end
198
208
  end
199
209
 
200
- # Returns the URI identifying the database, which may not be the
201
- # same as the URI used when connecting.
202
- # This method can raise an error if the database used options
203
- # instead of a connection string, and will not include uri
204
- # parameters.
205
- #
206
- # Sequel.connect('postgres://localhost/db?user=billg').url
207
- # # => "postgres://billg@localhost/db"
210
+ # Returns the URI use to connect to the database. If a URI
211
+ # was not used when connecting, returns nil.
208
212
  def uri
209
- uri = URI::Generic.new(
210
- adapter_scheme.to_s,
211
- nil,
212
- @opts[:host],
213
- @opts[:port],
214
- nil,
215
- "/#{@opts[:database]}",
216
- nil,
217
- nil,
218
- nil
219
- )
220
- uri.user = @opts[:user]
221
- uri.password = @opts[:password] if uri.user
222
- uri.to_s
213
+ opts[:uri]
223
214
  end
224
215
 
225
216
  # Explicit alias of uri for easier subclassing.
@@ -81,6 +81,24 @@ module Sequel
81
81
  execute_dui(sql, opts, &block)
82
82
  end
83
83
 
84
+ # Returns an array of hashes containing foreign key information from the
85
+ # table. Each hash will contain at least the following fields:
86
+ #
87
+ # :columns :: An array of columns in the given table
88
+ # :table :: The table referenced by the columns
89
+ # :key :: An array of columns referenced (in the table specified by :table),
90
+ # but can be nil on certain adapters if the primary key is referenced.
91
+ #
92
+ # The hash may also contain entries for:
93
+ #
94
+ # :deferrable :: Whether the constraint is deferrable
95
+ # :name :: The name of the constraint
96
+ # :on_delete :: The action to take ON DELETE
97
+ # :on_update :: The action to take ON UPDATE
98
+ def foreign_key_list(table, opts={})
99
+ raise NotImplemented, "#foreign_key_list should be overridden by adapters"
100
+ end
101
+
84
102
  # Returns a single value from the database, e.g.:
85
103
  #
86
104
  # DB.get(1) # SELECT 1
@@ -286,11 +304,11 @@ module Sequel
286
304
  if supports_savepoints?
287
305
  unless @transactions[conn]
288
306
  @transactions[conn] = {:savepoint_level=>0}
289
- @transactions[conn][:prepare] = opts[:prepare] if supports_prepared_transactions?
307
+ @transactions[conn][:prepare] = opts[:prepare] if opts[:prepare] && supports_prepared_transactions?
290
308
  end
291
309
  else
292
310
  @transactions[conn] = {}
293
- @transactions[conn][:prepare] = opts[:prepare] if supports_prepared_transactions?
311
+ @transactions[conn][:prepare] = opts[:prepare] if opts[:prepare] && supports_prepared_transactions?
294
312
  end
295
313
  end
296
314
 
@@ -92,9 +92,9 @@ module Sequel
92
92
  # or not allowing NULL values (if false). If unspecified, will default
93
93
  # to whatever the database default is.
94
94
  # :on_delete :: Specify the behavior of this column when being deleted
95
- # (:restrict, cascade, :set_null, :set_default, :no_action).
95
+ # (:restrict, :cascade, :set_null, :set_default, :no_action).
96
96
  # :on_update :: Specify the behavior of this column when being updated
97
- # (:restrict, cascade, :set_null, :set_default, :no_action).
97
+ # (:restrict, :cascade, :set_null, :set_default, :no_action).
98
98
  # :primary_key :: Make the column as a single primary key column. This should only
99
99
  # be used if you have a single, nonautoincrementing primary key column.
100
100
  # :size :: The size of the column, generally used with string
@@ -6,15 +6,10 @@ module Sequel
6
6
  # ---------------------
7
7
 
8
8
  AUTOINCREMENT = 'AUTOINCREMENT'.freeze
9
- CASCADE = 'CASCADE'.freeze
10
9
  COMMA_SEPARATOR = ', '.freeze
11
- NO_ACTION = 'NO ACTION'.freeze
12
10
  NOT_NULL = ' NOT NULL'.freeze
13
11
  NULL = ' NULL'.freeze
14
12
  PRIMARY_KEY = ' PRIMARY KEY'.freeze
15
- RESTRICT = 'RESTRICT'.freeze
16
- SET_DEFAULT = 'SET DEFAULT'.freeze
17
- SET_NULL = 'SET NULL'.freeze
18
13
  TEMPORARY = 'TEMPORARY '.freeze
19
14
  UNDERSCORE = '_'.freeze
20
15
  UNIQUE = ' UNIQUE'.freeze
@@ -23,6 +18,9 @@ module Sequel
23
18
  # The order of column modifiers to use when defining a column.
24
19
  COLUMN_DEFINITION_ORDER = [:collate, :default, :null, :unique, :primary_key, :auto_increment, :references]
25
20
 
21
+ # The default options for join table columns.
22
+ DEFAULT_JOIN_TABLE_COLUMN_OPTIONS = {:null=>false}
23
+
26
24
  # Adds a column to the specified table. This method expects a column name,
27
25
  # a datatype and optionally a hash with additional constraints and options:
28
26
  #
@@ -76,6 +74,54 @@ module Sequel
76
74
  nil
77
75
  end
78
76
 
77
+ # Create a join table using a hash of foreign keys to referenced
78
+ # table names. Example:
79
+ #
80
+ # create_join_table(:cat_id=>:cats, :dog_id=>:dogs)
81
+ # # CREATE TABLE cats_dogs (
82
+ # # cat_id integer NOT NULL REFERENCES cats,
83
+ # # dog_id integer NOT NULL REFERENCES dogs,
84
+ # # PRIMARY KEY (cat_id, dog_id)
85
+ # # )
86
+ # # CREATE INDEX cats_dogs_dog_id_cat_id_index ON cats_dogs(dog_id, cat_id)
87
+ #
88
+ # The primary key and index are used so that almost all operations
89
+ # on the table can benefit from one of the two indexes, and the primary
90
+ # key ensures that entries in the table are unique, which is the typical
91
+ # desire for a join table.
92
+ #
93
+ # You can provide column options by making the values in the hash
94
+ # be option hashes, so long as the option hashes have a :table
95
+ # entry giving the table referenced:
96
+ #
97
+ # create_join_table(:cat_id=>{:table=>:cats, :type=>Bignum}, :dog_id=>:dogs)
98
+ #
99
+ # You can provide a second argument which is a table options hash:
100
+ #
101
+ # create_join_table({:cat_id=>:cats, :dog_id=>:dogs}, :temp=>true)
102
+ #
103
+ # Some table options are handled specially:
104
+ #
105
+ # :index_options :: The options to pass to the index
106
+ # :name :: The name of the table to create
107
+ # :no_index :: Set to true not to create the second index.
108
+ # :no_primary_key :: Set to true to not create the primary key.
109
+ def create_join_table(hash, options={})
110
+ keys = hash.keys.sort_by{|k| k.to_s}
111
+ create_table(join_table_name(hash, options), options) do
112
+ keys.each do |key|
113
+ v = hash[key]
114
+ unless v.is_a?(Hash)
115
+ v = {:table=>v}
116
+ end
117
+ v = DEFAULT_JOIN_TABLE_COLUMN_OPTIONS.merge(v)
118
+ foreign_key(key, v)
119
+ end
120
+ primary_key(keys) unless options[:no_primary_key]
121
+ index(keys.reverse, options[:index_options] || {}) unless options[:no_index]
122
+ end
123
+ end
124
+
79
125
  # Creates a table with the columns given in the provided block:
80
126
  #
81
127
  # DB.create_table :posts do
@@ -102,18 +148,18 @@ module Sequel
102
148
  # Forcibly create a table, attempting to drop it if it already exists, then creating it.
103
149
  #
104
150
  # DB.create_table!(:a){Integer :a}
105
- # # SELECT * FROM a LIMIT a -- check existence
151
+ # # SELECT NULL FROM a LIMIT 1 -- check existence
106
152
  # # DROP TABLE a -- drop table if already exists
107
153
  # # CREATE TABLE a (a integer)
108
154
  def create_table!(name, options={}, &block)
109
- drop_table(name) if table_exists?(name)
155
+ drop_table?(name)
110
156
  create_table(name, options, &block)
111
157
  end
112
158
 
113
159
  # Creates the table unless the table already exists.
114
160
  #
115
161
  # DB.create_table?(:a){Integer :a}
116
- # # SELECT * FROM a LIMIT a -- check existence
162
+ # # SELECT NULL FROM a LIMIT 1 -- check existence
117
163
  # # CREATE TABLE a (a integer) -- if it doesn't already exist
118
164
  def create_table?(name, options={}, &block)
119
165
  if supports_create_table_if_not_exists?
@@ -161,10 +207,19 @@ module Sequel
161
207
  def drop_index(table, columns, options={})
162
208
  alter_table(table){drop_index(columns, options)}
163
209
  end
210
+
211
+ # Drop the join table that would have been created with the
212
+ # same arguments to create_join_table:
213
+ #
214
+ # drop_join_table(:cat_id=>:cats, :dog_id=>:dogs)
215
+ # # DROP TABLE cats_dogs
216
+ def drop_join_table(hash, options={})
217
+ drop_table(join_table_name(hash, options), options)
218
+ end
164
219
 
165
220
  # Drops one or more tables corresponding to the given names:
166
221
  #
167
- # DB.drop_table(:posts)
222
+ # DB.drop_table(:posts) # DROP TABLE posts
168
223
  # DB.drop_table(:posts, :comments)
169
224
  # DB.drop_table(:posts, :comments, :cascade=>true)
170
225
  def drop_table(*names)
@@ -176,6 +231,26 @@ module Sequel
176
231
  nil
177
232
  end
178
233
 
234
+ # Drops the table if it already exists. If it doesn't exist,
235
+ # does nothing.
236
+ #
237
+ # DB.drop_table?(:a)
238
+ # # SELECT NULL FROM a LIMIT 1 -- check existence
239
+ # # DROP TABLE a -- if it already exists
240
+ def drop_table?(*names)
241
+ options = names.last.is_a?(Hash) ? names.pop : {}
242
+ if supports_drop_table_if_exists?
243
+ options = options.merge(:if_exists=>true)
244
+ names.each do |name|
245
+ drop_table(name, options)
246
+ end
247
+ else
248
+ names.each do |name|
249
+ drop_table(name, options) if table_exists?(name)
250
+ end
251
+ end
252
+ end
253
+
179
254
  # Drops one or more views corresponding to the given names:
180
255
  #
181
256
  # DB.drop_view(:cheap_items)
@@ -343,7 +418,7 @@ module Sequel
343
418
  sql = " REFERENCES #{quote_schema_table(column[:table])}"
344
419
  sql << "(#{Array(column[:key]).map{|x| quote_identifier(x)}.join(COMMA_SEPARATOR)})" if column[:key]
345
420
  sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
346
- sql << " ON UPDATE #{on_delete_clause(column[:on_update])}" if column[:on_update]
421
+ sql << " ON UPDATE #{on_update_clause(column[:on_update])}" if column[:on_update]
347
422
  sql << " DEFERRABLE INITIALLY DEFERRED" if column[:deferrable]
348
423
  sql
349
424
  end
@@ -408,7 +483,7 @@ module Sequel
408
483
 
409
484
  # SQL DDL statement to drop the table with the given name.
410
485
  def drop_table_sql(name, options)
411
- "DROP TABLE #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
486
+ "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
412
487
  end
413
488
 
414
489
  # SQL DDL statement to drop a view with the given name.
@@ -440,6 +515,32 @@ module Sequel
440
515
  indexes.map{|i| index_definition_sql(table_name, i)}
441
516
  end
442
517
 
518
+ # Extract the join table name from the arguments given to create_join_table.
519
+ # Also does argument validation for the create_join_table method.
520
+ def join_table_name(hash, options)
521
+ entries = hash.values
522
+ raise Error, "must have 2 entries in hash given to (create|drop)_join_table" unless entries.length == 2
523
+ if options[:name]
524
+ options[:name]
525
+ else
526
+ table_names = entries.map{|e| join_table_name_extract(e)}
527
+ table_names.map{|t| t.to_s}.sort.join('_')
528
+ end
529
+ end
530
+
531
+ # Extract an individual join table name, which should either be a string
532
+ # or symbol, or a hash containing one of those as the value for :table.
533
+ def join_table_name_extract(entry)
534
+ case entry
535
+ when Symbol, String
536
+ entry
537
+ when Hash
538
+ join_table_name_extract(entry[:table])
539
+ else
540
+ raise Error, "can't extract table name from #{entry.inspect}"
541
+ end
542
+ end
543
+
443
544
  # SQL DDL ON DELETE fragment to use, based on the given action.
444
545
  # The following actions are recognized:
445
546
  #
@@ -450,19 +551,15 @@ module Sequel
450
551
  # but do not allow deferring the integrity check.
451
552
  # * :set_default - Set columns referencing this row to their default value.
452
553
  # * :set_null - Set columns referencing this row to NULL.
554
+ #
555
+ # Any other object given is just converted to a string, with "_" converted to " " and upcased.
453
556
  def on_delete_clause(action)
454
- case action
455
- when :restrict
456
- RESTRICT
457
- when :cascade
458
- CASCADE
459
- when :set_null
460
- SET_NULL
461
- when :set_default
462
- SET_DEFAULT
463
- else
464
- NO_ACTION
465
- end
557
+ action.to_s.gsub("_", " ").upcase
558
+ end
559
+
560
+ # Alias of #on_delete_clause, since the two usually behave the same.
561
+ def on_update_clause(action)
562
+ on_delete_clause(action)
466
563
  end
467
564
 
468
565
  # Proxy the quote_schema_table method to the dataset