sequel 3.33.0 → 3.34.0

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