sequel 3.32.0 → 3.33.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +42 -0
- data/Rakefile +3 -2
- data/doc/migration.rdoc +41 -14
- data/doc/opening_databases.rdoc +2 -0
- data/doc/release_notes/3.29.0.txt +1 -1
- data/doc/release_notes/3.33.0.txt +157 -0
- data/doc/sharding.rdoc +93 -7
- data/doc/testing.rdoc +1 -1
- data/lib/sequel/adapters/do.rb +1 -0
- data/lib/sequel/adapters/do/mysql.rb +6 -0
- data/lib/sequel/adapters/jdbc.rb +1 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
- data/lib/sequel/adapters/mock.rb +10 -5
- data/lib/sequel/adapters/mysql.rb +23 -1
- data/lib/sequel/adapters/mysql2.rb +16 -2
- data/lib/sequel/adapters/postgres.rb +22 -4
- data/lib/sequel/adapters/shared/mysql.rb +51 -10
- data/lib/sequel/adapters/shared/postgres.rb +101 -63
- data/lib/sequel/adapters/shared/sqlite.rb +19 -0
- data/lib/sequel/adapters/sqlite.rb +71 -16
- data/lib/sequel/adapters/swift.rb +1 -0
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/connection_pool/sharded_single.rb +6 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +6 -1
- data/lib/sequel/connection_pool/threaded.rb +12 -11
- data/lib/sequel/database/connecting.rb +2 -0
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
- data/lib/sequel/extensions/migration.rb +45 -7
- data/lib/sequel/extensions/server_block.rb +139 -0
- data/lib/sequel/model/associations.rb +9 -9
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +1 -1
- data/lib/sequel/plugins/list.rb +12 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +64 -8
- data/spec/adapters/postgres_spec.rb +139 -78
- data/spec/adapters/sqlite_spec.rb +87 -0
- data/spec/core/connection_pool_spec.rb +14 -0
- data/spec/core/database_spec.rb +5 -0
- data/spec/core/mock_adapter_spec.rb +21 -9
- data/spec/extensions/arbitrary_servers_spec.rb +114 -0
- data/spec/extensions/instance_hooks_spec.rb +19 -0
- data/spec/extensions/list_spec.rb +31 -0
- data/spec/extensions/migration_spec.rb +61 -4
- data/spec/extensions/server_block_spec.rb +90 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/files/transaction_migrations/001_create_alt_basic.rb +3 -0
- data/spec/files/transaction_migrations/002_create_basic.rb +3 -0
- data/spec/files/transactionless_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/transactionless_migrations/002_create_basic.rb +4 -0
- data/spec/integration/dataset_test.rb +2 -2
- data/spec/integration/plugin_test.rb +9 -9
- data/spec/integration/schema_test.rb +3 -1
- data/spec/integration/transaction_test.rb +2 -2
- metadata +12 -2
data/doc/testing.rdoc
CHANGED
@@ -106,7 +106,7 @@ These specs are broken down into two parts. For each database, there are specif
|
|
106
106
|
|
107
107
|
== Environment variables
|
108
108
|
|
109
|
-
Sequel often uses environment variables when testing to specify either the database to be tested or specify how testing should be done. You can also specify the databases to test by copying spec/spec_config.rb.example to spec/spec_config.rb and modifying it. See that file for details. It may be necessary to use spec_config.rb as opposed to an environment variable if
|
109
|
+
Sequel often uses environment variables when testing to specify either the database to be tested or specify how testing should be done. You can also specify the databases to test by copying spec/spec_config.rb.example to spec/spec_config.rb and modifying it. See that file for details. It may be necessary to use spec_config.rb as opposed to an environment variable if your database connection cannot be specified by a connection string.
|
110
110
|
|
111
111
|
=== Connection Strings
|
112
112
|
|
data/lib/sequel/adapters/do.rb
CHANGED
@@ -30,6 +30,12 @@ module Sequel
|
|
30
30
|
APOS_RE = Dataset::APOS_RE
|
31
31
|
DOUBLE_APOS = Dataset::DOUBLE_APOS
|
32
32
|
|
33
|
+
# The DataObjects MySQL driver uses the number of rows actually modified in the update,
|
34
|
+
# instead of the number of matched by the filter.
|
35
|
+
def provides_accurate_rows_matched?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
33
39
|
# Use execute_insert to execute the replace_sql.
|
34
40
|
def replace(*args)
|
35
41
|
execute_insert(replace_sql(*args))
|
data/lib/sequel/adapters/jdbc.rb
CHANGED
data/lib/sequel/adapters/mock.rb
CHANGED
@@ -110,6 +110,7 @@ module Sequel
|
|
110
110
|
super
|
111
111
|
opts = @opts
|
112
112
|
if mod_name = SHARED_ADAPTERS[opts[:host]]
|
113
|
+
@shared_adapter = true
|
113
114
|
require "sequel/adapters/shared/#{opts[:host]}"
|
114
115
|
extend Sequel.const_get(mod_name)::DatabaseMethods
|
115
116
|
extend_datasets Sequel.const_get(mod_name)::DatasetMethods
|
@@ -155,7 +156,7 @@ module Sequel
|
|
155
156
|
|
156
157
|
# Enable use of savepoints.
|
157
158
|
def supports_savepoints?
|
158
|
-
true
|
159
|
+
shared_adapter? ? super : true
|
159
160
|
end
|
160
161
|
|
161
162
|
private
|
@@ -177,7 +178,7 @@ module Sequel
|
|
177
178
|
def _execute(c, sql, opts={}, &block)
|
178
179
|
sql += " -- args: #{opts[:arguments].inspect}" if opts[:arguments]
|
179
180
|
sql += " -- #{@opts[:append]}" if @opts[:append]
|
180
|
-
sql += " -- #{c.server}" if c.server != :default
|
181
|
+
sql += " -- #{c.server.is_a?(Symbol) ? c.server : c.server.inspect}" if c.server != :default
|
181
182
|
log_info(sql)
|
182
183
|
@sqls << sql
|
183
184
|
|
@@ -281,15 +282,19 @@ module Sequel
|
|
281
282
|
end
|
282
283
|
|
283
284
|
def quote_identifiers_default
|
284
|
-
false
|
285
|
+
shared_adapter? ? super : false
|
285
286
|
end
|
286
287
|
|
287
288
|
def identifier_input_method_default
|
288
|
-
nil
|
289
|
+
shared_adapter? ? super : nil
|
289
290
|
end
|
290
291
|
|
291
292
|
def identifier_output_method_default
|
292
|
-
nil
|
293
|
+
shared_adapter? ? super : nil
|
294
|
+
end
|
295
|
+
|
296
|
+
def shared_adapter?
|
297
|
+
@shared_adapter
|
293
298
|
end
|
294
299
|
end
|
295
300
|
|
@@ -268,6 +268,10 @@ module Sequel
|
|
268
268
|
include Sequel::MySQL::PreparedStatements::DatasetMethods
|
269
269
|
|
270
270
|
Database::DatasetClass = self
|
271
|
+
|
272
|
+
# Regular expression used for getting accurate number of rows
|
273
|
+
# matched by an update statement.
|
274
|
+
AFFECTED_ROWS_RE = /Rows matched:\s+(\d+)\s+Changed:\s+\d+\s+Warnings:\s+\d+/.freeze
|
271
275
|
|
272
276
|
# Delete rows matching this dataset
|
273
277
|
def delete
|
@@ -311,6 +315,12 @@ module Sequel
|
|
311
315
|
execute_dui(insert_sql(*values)){|c| return c.insert_id}
|
312
316
|
end
|
313
317
|
|
318
|
+
# You can parse out the correct number of rows matched using the query info,
|
319
|
+
# even though affected_rows doesn't provide an accurate number.
|
320
|
+
def provides_accurate_rows_matched?
|
321
|
+
true
|
322
|
+
end
|
323
|
+
|
314
324
|
# Replace (update or insert) the matching row.
|
315
325
|
def replace(*args)
|
316
326
|
execute_dui(replace_sql(*args)){|c| return c.insert_id}
|
@@ -334,11 +344,23 @@ module Sequel
|
|
334
344
|
|
335
345
|
# Update the matching rows.
|
336
346
|
def update(values={})
|
337
|
-
execute_dui(update_sql(values)){|c| return c
|
347
|
+
execute_dui(update_sql(values)){|c| return affected_rows(c)}
|
338
348
|
end
|
339
349
|
|
340
350
|
private
|
341
351
|
|
352
|
+
# Try to get an accurate number of rows matched using the query
|
353
|
+
# info. Fall back to affected_rows if there was no match, but
|
354
|
+
# that may be inaccurate.
|
355
|
+
def affected_rows(conn)
|
356
|
+
s = conn.info
|
357
|
+
if s && s =~ AFFECTED_ROWS_RE
|
358
|
+
$1.to_i
|
359
|
+
else
|
360
|
+
conn.affected_rows
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
342
364
|
# Set the :type option to :select if it hasn't been set.
|
343
365
|
def execute(sql, opts={}, &block)
|
344
366
|
super(sql, {:type=>:select}.merge(opts), &block)
|
@@ -45,6 +45,7 @@ module Sequel
|
|
45
45
|
opts = server_opts(server)
|
46
46
|
opts[:host] ||= 'localhost'
|
47
47
|
opts[:username] ||= opts[:user]
|
48
|
+
opts[:flags] = ::Mysql2::Client::FOUND_ROWS if ::Mysql2::Client.const_defined?(:FOUND_ROWS)
|
48
49
|
conn = ::Mysql2::Client.new(opts)
|
49
50
|
|
50
51
|
sqls = []
|
@@ -138,8 +139,21 @@ module Sequel
|
|
138
139
|
# Yield all rows matching this dataset.
|
139
140
|
def fetch_rows(sql, &block)
|
140
141
|
execute(sql) do |r|
|
141
|
-
|
142
|
-
|
142
|
+
if identifier_output_method
|
143
|
+
cols = r.fields
|
144
|
+
@columns = cols2 = cols.map{|c| output_identifier(c.to_s)}
|
145
|
+
cs = cols.zip(cols2)
|
146
|
+
r.each(:cast_booleans => db.convert_tinyint_to_bool) do |row|
|
147
|
+
h = {}
|
148
|
+
cs.each do |a, b|
|
149
|
+
h[b] = row[a]
|
150
|
+
end
|
151
|
+
yield h
|
152
|
+
end
|
153
|
+
else
|
154
|
+
@columns = r.fields
|
155
|
+
r.each(:cast_booleans => db.convert_tinyint_to_bool, &block)
|
156
|
+
end
|
143
157
|
end
|
144
158
|
self
|
145
159
|
end
|
@@ -88,11 +88,26 @@ module Sequel
|
|
88
88
|
module Postgres
|
89
89
|
CONVERTED_EXCEPTIONS << PGError
|
90
90
|
|
91
|
+
NAN = 0.0/0.0
|
92
|
+
PLUS_INFINITY = 1.0/0.0
|
93
|
+
MINUS_INFINITY = -1.0/0.0
|
94
|
+
|
91
95
|
TYPE_TRANSLATOR = tt = Class.new do
|
92
96
|
def boolean(s) s == 't' end
|
93
97
|
def bytea(s) ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s)) end
|
94
98
|
def integer(s) s.to_i end
|
95
|
-
def float(s)
|
99
|
+
def float(s)
|
100
|
+
case s
|
101
|
+
when 'NaN'
|
102
|
+
NAN
|
103
|
+
when 'Infinity'
|
104
|
+
PLUS_INFINITY
|
105
|
+
when '-Infinity'
|
106
|
+
MINUS_INFINITY
|
107
|
+
else
|
108
|
+
s.to_f
|
109
|
+
end
|
110
|
+
end
|
96
111
|
def date(s) ::Date.new(*s.split("-").map{|x| x.to_i}) end
|
97
112
|
end.new
|
98
113
|
|
@@ -215,18 +230,21 @@ module Sequel
|
|
215
230
|
|
216
231
|
# Connects to the database. In addition to the standard database
|
217
232
|
# options, using the :encoding or :charset option changes the
|
218
|
-
# client encoding for the connection
|
233
|
+
# client encoding for the connection, :connect_timeout is a
|
234
|
+
# connection timeout in seconds, and :sslmode sets whether postgres's
|
235
|
+
# sslmode. :connect_timeout and :ssl_mode are only supported if the pg
|
236
|
+
# driver is used.
|
219
237
|
def connect(server)
|
220
238
|
opts = server_opts(server)
|
221
239
|
conn = if SEQUEL_POSTGRES_USES_PG
|
222
240
|
connection_params = {
|
223
241
|
:host => opts[:host],
|
224
242
|
:port => opts[:port] || 5432,
|
225
|
-
:tty => '',
|
226
243
|
:dbname => opts[:database],
|
227
244
|
:user => opts[:user],
|
228
245
|
:password => opts[:password],
|
229
|
-
:connect_timeout => opts[:connect_timeout] || 20
|
246
|
+
:connect_timeout => opts[:connect_timeout] || 20,
|
247
|
+
:sslmode => opts[:sslmode]
|
230
248
|
}.delete_if { |key, value| blank_object?(value) }
|
231
249
|
Adapter.connect(connection_params)
|
232
250
|
else
|
@@ -31,7 +31,7 @@ module Sequel
|
|
31
31
|
module DatabaseMethods
|
32
32
|
AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
|
33
33
|
CAST_TYPES = {String=>:CHAR, Integer=>:SIGNED, Time=>:DATETIME, DateTime=>:DATETIME, Numeric=>:DECIMAL, BigDecimal=>:DECIMAL, File=>:BINARY}
|
34
|
-
COLUMN_DEFINITION_ORDER = [:null, :default, :unique, :primary_key, :auto_increment, :references]
|
34
|
+
COLUMN_DEFINITION_ORDER = [:collate, :null, :default, :unique, :primary_key, :auto_increment, :references]
|
35
35
|
PRIMARY = 'PRIMARY'.freeze
|
36
36
|
|
37
37
|
# MySQL's cast rules are restrictive in that you can't just cast to any possible
|
@@ -91,12 +91,18 @@ module Sequel
|
|
91
91
|
|
92
92
|
# MySQL supports prepared transactions (two-phase commit) using XA
|
93
93
|
def supports_prepared_transactions?
|
94
|
-
|
94
|
+
server_version >= 50000
|
95
95
|
end
|
96
96
|
|
97
97
|
# MySQL supports savepoints
|
98
98
|
def supports_savepoints?
|
99
|
-
|
99
|
+
server_version >= 50000
|
100
|
+
end
|
101
|
+
|
102
|
+
# MySQL doesn't appear to support savepoints inside prepared transactions in >=5.5.12,
|
103
|
+
# see http://bugs.mysql.com/bug.php?id=64374
|
104
|
+
def supports_savepoints_in_prepared_transactions?
|
105
|
+
super && server_version <= 50512
|
100
106
|
end
|
101
107
|
|
102
108
|
# MySQL supports transaction isolation levels
|
@@ -139,6 +145,7 @@ module Sequel
|
|
139
145
|
if related = op.delete(:table)
|
140
146
|
sql = super(table, op)
|
141
147
|
op[:table] = related
|
148
|
+
op[:key] ||= primary_key_from_schema(related)
|
142
149
|
[sql, "ALTER TABLE #{quote_schema_table(table)} ADD FOREIGN KEY (#{quote_identifier(op[:name])})#{column_references_sql(op)}"]
|
143
150
|
else
|
144
151
|
super(table, op)
|
@@ -167,6 +174,11 @@ module Sequel
|
|
167
174
|
raise(Error, "must specify constraint type via :type=>(:foreign_key|:primary_key|:unique) when dropping constraints on MySQL")
|
168
175
|
end
|
169
176
|
"ALTER TABLE #{quote_schema_table(table)} DROP #{type} #{quote_identifier(op[:name])}"
|
177
|
+
when :add_constraint
|
178
|
+
if op[:type] == :foreign_key
|
179
|
+
op[:key] ||= primary_key_from_schema(op[:table])
|
180
|
+
end
|
181
|
+
super(table, op)
|
170
182
|
else
|
171
183
|
super(table, op)
|
172
184
|
end
|
@@ -222,11 +234,41 @@ module Sequel
|
|
222
234
|
engine = options.fetch(:engine, Sequel::MySQL.default_engine)
|
223
235
|
charset = options.fetch(:charset, Sequel::MySQL.default_charset)
|
224
236
|
collate = options.fetch(:collate, Sequel::MySQL.default_collate)
|
237
|
+
generator.constraints.sort_by{|c| (c[:type] == :primary_key) ? -1 : 1}
|
238
|
+
|
239
|
+
key_proc = lambda do |t|
|
240
|
+
if t == name
|
241
|
+
if pk = generator.primary_key_name
|
242
|
+
[pk]
|
243
|
+
elsif !(pkc = generator.constraints.select{|con| con[:type] == :primary_key}).empty?
|
244
|
+
pkc.first[:columns]
|
245
|
+
end
|
246
|
+
else
|
247
|
+
primary_key_from_schema(t)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
generator.constraints.each do |c|
|
252
|
+
if c[:type] == :foreign_key
|
253
|
+
c[:key] ||= key_proc.call(c[:table])
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
225
257
|
generator.columns.each do |c|
|
226
258
|
if t = c.delete(:table)
|
227
|
-
|
259
|
+
same_table = t == name
|
260
|
+
k = c[:key]
|
261
|
+
|
262
|
+
key ||= key_proc.call(t)
|
263
|
+
|
264
|
+
if same_table && !k.nil?
|
265
|
+
generator.constraints.unshift(:type=>:unique, :columns=>Array(k))
|
266
|
+
end
|
267
|
+
|
268
|
+
generator.foreign_key([c[:name]], t, c.merge(:name=>nil, :type=>:foreign_key, :key=>key))
|
228
269
|
end
|
229
270
|
end
|
271
|
+
|
230
272
|
"#{super}#{" ENGINE=#{engine}" if engine}#{" DEFAULT CHARSET=#{charset}" if charset}#{" DEFAULT COLLATE=#{collate}" if collate}"
|
231
273
|
end
|
232
274
|
|
@@ -261,6 +303,11 @@ module Sequel
|
|
261
303
|
"CREATE #{index_type}INDEX #{index_name}#{using} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
|
262
304
|
end
|
263
305
|
|
306
|
+
# Parse the schema for the given table to get an array of primary key columns
|
307
|
+
def primary_key_from_schema(table)
|
308
|
+
schema(table).select{|a| a[1][:primary_key]}.map{|a| a[0]}
|
309
|
+
end
|
310
|
+
|
264
311
|
# Rollback the currently open XA transaction
|
265
312
|
def rollback_transaction(conn, opts={})
|
266
313
|
if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
|
@@ -503,12 +550,6 @@ module Sequel
|
|
503
550
|
[insert_sql(columns, sql)]
|
504
551
|
end
|
505
552
|
|
506
|
-
# MySQL uses the number of rows actually modified in the update,
|
507
|
-
# instead of the number of matched by the filter.
|
508
|
-
def provides_accurate_rows_matched?
|
509
|
-
false
|
510
|
-
end
|
511
|
-
|
512
553
|
# MySQL uses the nonstandard ` (backtick) for quoting identifiers.
|
513
554
|
def quoted_identifier_append(sql, c)
|
514
555
|
sql << BACKTICK << c.to_s << BACKTICK
|
@@ -33,10 +33,10 @@ module Sequel
|
|
33
33
|
# Array of exceptions that need to be converted. JDBC
|
34
34
|
# uses NativeExceptions, the native adapter uses PGError.
|
35
35
|
CONVERTED_EXCEPTIONS = []
|
36
|
-
|
36
|
+
|
37
37
|
@client_min_messages = :warning
|
38
38
|
@force_standard_strings = true
|
39
|
-
|
39
|
+
|
40
40
|
class << self
|
41
41
|
# By default, Sequel sets the minimum level of log messages sent to the client
|
42
42
|
# to WARNING, where PostgreSQL uses a default of NOTICE. This is to avoid a lot
|
@@ -55,14 +55,14 @@ module Sequel
|
|
55
55
|
# Methods shared by adapter/connection instances.
|
56
56
|
module AdapterMethods
|
57
57
|
attr_writer :db
|
58
|
-
|
58
|
+
|
59
59
|
SELECT_CURRVAL = "SELECT currval('%s')".freeze
|
60
60
|
SELECT_CUSTOM_SEQUENCE = proc do |schema, table| <<-end_sql
|
61
|
-
SELECT '"' || name.nspname || '".' || CASE
|
62
|
-
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
63
|
-
substr(split_part(def.adsrc, '''', 2),
|
64
|
-
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
65
|
-
ELSE split_part(def.adsrc, '''', 2)
|
61
|
+
SELECT '"' || name.nspname || '".' || CASE
|
62
|
+
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
63
|
+
substr(split_part(def.adsrc, '''', 2),
|
64
|
+
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
65
|
+
ELSE split_part(def.adsrc, '''', 2)
|
66
66
|
END
|
67
67
|
FROM pg_class t
|
68
68
|
JOIN pg_namespace name ON (t.relnamespace = name.oid)
|
@@ -103,11 +103,11 @@ module Sequel
|
|
103
103
|
AND seq.relname = '#{table}'
|
104
104
|
end_sql
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
# Depth of the current transaction on this connection, used
|
108
108
|
# to implement multi-level transactions with savepoints.
|
109
109
|
attr_accessor :transaction_depth
|
110
|
-
|
110
|
+
|
111
111
|
# Apply connection settings for this connection. Currently, turns
|
112
112
|
# standard_conforming_strings ON if Postgres.force_standard_strings
|
113
113
|
# is true.
|
@@ -131,7 +131,7 @@ module Sequel
|
|
131
131
|
return val.to_i if val
|
132
132
|
end
|
133
133
|
end
|
134
|
-
|
134
|
+
|
135
135
|
# Get the primary key for the given table.
|
136
136
|
def primary_key(schema, table)
|
137
137
|
sql = SELECT_PK[schema, table]
|
@@ -139,7 +139,7 @@ module Sequel
|
|
139
139
|
return single_value(r)
|
140
140
|
end
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
# Get the primary key and sequence for the given table.
|
144
144
|
def sequence(schema, table)
|
145
145
|
sql = SELECT_SERIAL_SEQUENCE[schema, table]
|
@@ -147,14 +147,14 @@ module Sequel
|
|
147
147
|
seq = single_value(r)
|
148
148
|
return seq if seq
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
sql = SELECT_CUSTOM_SEQUENCE[schema, table]
|
152
152
|
execute(sql) do |r|
|
153
153
|
return single_value(r)
|
154
154
|
end
|
155
155
|
end
|
156
156
|
end
|
157
|
-
|
157
|
+
|
158
158
|
# Methods shared by Database instances that connect to PostgreSQL.
|
159
159
|
module DatabaseMethods
|
160
160
|
EXCLUDE_SCHEMAS = /pg_*|information_schema/i
|
@@ -191,7 +191,7 @@ module Sequel
|
|
191
191
|
def create_function(name, definition, opts={})
|
192
192
|
self << create_function_sql(name, definition, opts)
|
193
193
|
end
|
194
|
-
|
194
|
+
|
195
195
|
# Create the procedural language in the database. Arguments:
|
196
196
|
# * name : Name of the procedural language (e.g. plpgsql)
|
197
197
|
# * opts : options hash:
|
@@ -202,7 +202,13 @@ module Sequel
|
|
202
202
|
def create_language(name, opts={})
|
203
203
|
self << create_language_sql(name, opts)
|
204
204
|
end
|
205
|
-
|
205
|
+
|
206
|
+
# Create a schema in the database. Arguments:
|
207
|
+
# * name : Name of the schema (e.g. admin)
|
208
|
+
def create_schema(name)
|
209
|
+
self << create_schema_sql(name)
|
210
|
+
end
|
211
|
+
|
206
212
|
# Create a trigger in the database. Arguments:
|
207
213
|
# * table : the table on which this trigger operates
|
208
214
|
# * name : the name of this trigger
|
@@ -216,7 +222,7 @@ module Sequel
|
|
216
222
|
def create_trigger(table, name, function, opts={})
|
217
223
|
self << create_trigger_sql(table, name, function, opts)
|
218
224
|
end
|
219
|
-
|
225
|
+
|
220
226
|
# PostgreSQL uses the :postgres database type.
|
221
227
|
def database_type
|
222
228
|
:postgres
|
@@ -231,7 +237,7 @@ module Sequel
|
|
231
237
|
def drop_function(name, opts={})
|
232
238
|
self << drop_function_sql(name, opts)
|
233
239
|
end
|
234
|
-
|
240
|
+
|
235
241
|
# Drops a procedural language from the database. Arguments:
|
236
242
|
# * name : name of the procedural language to drop
|
237
243
|
# * opts : options hash:
|
@@ -240,7 +246,16 @@ module Sequel
|
|
240
246
|
def drop_language(name, opts={})
|
241
247
|
self << drop_language_sql(name, opts)
|
242
248
|
end
|
243
|
-
|
249
|
+
|
250
|
+
# Drops a schema from the database. Arguments:
|
251
|
+
# * name : name of the schema to drop
|
252
|
+
# * opts : options hash:
|
253
|
+
# * :cascade : Drop all objects in this schema.
|
254
|
+
# * :if_exists : Don't raise an error if the schema doesn't exist.
|
255
|
+
def drop_schema(name, opts={})
|
256
|
+
self << drop_schema_sql(name, opts)
|
257
|
+
end
|
258
|
+
|
244
259
|
# Drops a trigger from the database. Arguments:
|
245
260
|
# * table : table from which to drop the trigger
|
246
261
|
# * name : name of the trigger to drop
|
@@ -250,7 +265,7 @@ module Sequel
|
|
250
265
|
def drop_trigger(table, name, opts={})
|
251
266
|
self << drop_trigger_sql(table, name, opts)
|
252
267
|
end
|
253
|
-
|
268
|
+
|
254
269
|
# Use the pg_* system tables to determine indexes on a table
|
255
270
|
def indexes(table, opts={})
|
256
271
|
m = output_identifier_meth
|
@@ -266,11 +281,11 @@ module Sequel
|
|
266
281
|
filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil).
|
267
282
|
order(:indc__relname, range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}.case(32, :att__attnum)).
|
268
283
|
select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column)
|
269
|
-
|
284
|
+
|
270
285
|
ds.join!(:pg_namespace___nsp, :oid=>:tab__relnamespace, :nspname=>schema.to_s) if schema
|
271
286
|
ds.filter!(:indisvalid=>true) if server_version >= 80200
|
272
287
|
ds.filter!(:indisready=>true, :indcheckxmin=>false) if server_version >= 80300
|
273
|
-
|
288
|
+
|
274
289
|
indexes = {}
|
275
290
|
ds.each do |r|
|
276
291
|
i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique]}
|
@@ -279,11 +294,11 @@ module Sequel
|
|
279
294
|
indexes
|
280
295
|
end
|
281
296
|
|
282
|
-
# Dataset containing all current database locks
|
297
|
+
# Dataset containing all current database locks
|
283
298
|
def locks
|
284
299
|
dataset.from(:pg_class).join(:pg_locks, :relation=>:relfilenode).select(:pg_class__relname, Sequel::SQL::ColumnAll.new(:pg_locks))
|
285
300
|
end
|
286
|
-
|
301
|
+
|
287
302
|
# Notifies the given channel. See the PostgreSQL NOTIFY documentation. Options:
|
288
303
|
#
|
289
304
|
# :payload :: The payload string to use for the NOTIFY statement. Only supported
|
@@ -304,7 +319,7 @@ module Sequel
|
|
304
319
|
synchronize(opts[:server]){|con| con.primary_key(*schema_and_table(table))}
|
305
320
|
end
|
306
321
|
end
|
307
|
-
|
322
|
+
|
308
323
|
# Return the sequence providing the default for the primary key for the given table.
|
309
324
|
def primary_key_sequence(table, opts={})
|
310
325
|
quoted_table = quote_schema_table(table)
|
@@ -315,7 +330,7 @@ module Sequel
|
|
315
330
|
synchronize(opts[:server]){|con| con.sequence(*schema_and_table(table))}
|
316
331
|
end
|
317
332
|
end
|
318
|
-
|
333
|
+
|
319
334
|
# Reset the primary key sequence for the given table, baseing it on the
|
320
335
|
# maximum current value of the table's primary key.
|
321
336
|
def reset_primary_key_sequence(table)
|
@@ -337,7 +352,7 @@ module Sequel
|
|
337
352
|
def serial_primary_key_options
|
338
353
|
{:primary_key => true, :serial => true, :type=>Integer}
|
339
354
|
end
|
340
|
-
|
355
|
+
|
341
356
|
# The version of the PostgreSQL server, used for determining capability.
|
342
357
|
def server_version(server=nil)
|
343
358
|
return @server_version if @server_version
|
@@ -353,7 +368,7 @@ module Sequel
|
|
353
368
|
end
|
354
369
|
@server_version
|
355
370
|
end
|
356
|
-
|
371
|
+
|
357
372
|
# PostgreSQL supports prepared transactions (two-phase commit) if
|
358
373
|
# max_prepared_transactions is greater than 0.
|
359
374
|
def supports_prepared_transactions?
|
@@ -378,7 +393,7 @@ module Sequel
|
|
378
393
|
|
379
394
|
# Array of symbols specifying table names in the current database.
|
380
395
|
# The dataset used is yielded to the block if one is provided,
|
381
|
-
# otherwise, an array of symbols of table names is returned.
|
396
|
+
# otherwise, an array of symbols of table names is returned.
|
382
397
|
#
|
383
398
|
# Options:
|
384
399
|
# * :schema - The schema to search (default_schema by default)
|
@@ -428,34 +443,44 @@ module Sequel
|
|
428
443
|
AS #{literal(definition.to_s)}#{", #{literal(opts[:link_symbol].to_s)}" if opts[:link_symbol]}
|
429
444
|
END
|
430
445
|
end
|
431
|
-
|
446
|
+
|
432
447
|
# SQL for creating a procedural language.
|
433
448
|
def create_language_sql(name, opts={})
|
434
449
|
"CREATE#{' OR REPLACE' if opts[:replace] && server_version >= 90000}#{' TRUSTED' if opts[:trusted]} LANGUAGE #{name}#{" HANDLER #{opts[:handler]}" if opts[:handler]}#{" VALIDATOR #{opts[:validator]}" if opts[:validator]}"
|
435
450
|
end
|
436
|
-
|
437
|
-
# SQL for creating a
|
451
|
+
|
452
|
+
# SQL for creating a schema.
|
453
|
+
def create_schema_sql(name)
|
454
|
+
"CREATE SCHEMA #{quote_identifier(name)}"
|
455
|
+
end
|
456
|
+
|
457
|
+
# SQL for creating a database trigger.
|
438
458
|
def create_trigger_sql(table, name, function, opts={})
|
439
459
|
events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
|
440
460
|
whence = opts[:after] ? 'AFTER' : 'BEFORE'
|
441
461
|
"CREATE TRIGGER #{name} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} ON #{quote_schema_table(table)}#{' FOR EACH ROW' if opts[:each_row]} EXECUTE PROCEDURE #{function}(#{Array(opts[:args]).map{|a| literal(a)}.join(', ')})"
|
442
462
|
end
|
443
|
-
|
463
|
+
|
444
464
|
# The errors that the main adapters can raise, depends on the adapter being used
|
445
465
|
def database_error_classes
|
446
466
|
CONVERTED_EXCEPTIONS
|
447
467
|
end
|
448
|
-
|
449
|
-
# SQL for dropping a function from the database.
|
468
|
+
|
469
|
+
# SQL for dropping a function from the database.
|
450
470
|
def drop_function_sql(name, opts={})
|
451
471
|
"DROP FUNCTION#{' IF EXISTS' if opts[:if_exists]} #{name}#{sql_function_args(opts[:args])}#{' CASCADE' if opts[:cascade]}"
|
452
472
|
end
|
453
|
-
|
473
|
+
|
454
474
|
# SQL for dropping a procedural language from the database.
|
455
475
|
def drop_language_sql(name, opts={})
|
456
476
|
"DROP LANGUAGE#{' IF EXISTS' if opts[:if_exists]} #{name}#{' CASCADE' if opts[:cascade]}"
|
457
477
|
end
|
458
478
|
|
479
|
+
# SQL for dropping a schema from the database.
|
480
|
+
def drop_schema_sql(name, opts={})
|
481
|
+
"DROP SCHEMA#{' IF EXISTS' if opts[:if_exists]} #{quote_identifier(name)}#{' CASCADE' if opts[:cascade]}"
|
482
|
+
end
|
483
|
+
|
459
484
|
# SQL for dropping a trigger from the database.
|
460
485
|
def drop_trigger_sql(table, name, opts={})
|
461
486
|
"DROP TRIGGER#{' IF EXISTS' if opts[:if_exists]} #{name} ON #{quote_schema_table(table)}#{' CASCADE' if opts[:cascade]}"
|
@@ -470,12 +495,12 @@ module Sequel
|
|
470
495
|
ds.exclude(:pg_namespace__nspname=>EXCLUDE_SCHEMAS)
|
471
496
|
end
|
472
497
|
end
|
473
|
-
|
498
|
+
|
474
499
|
# PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
|
475
500
|
def identifier_input_method_default
|
476
501
|
nil
|
477
502
|
end
|
478
|
-
|
503
|
+
|
479
504
|
# PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
|
480
505
|
def identifier_output_method_default
|
481
506
|
nil
|
@@ -485,7 +510,7 @@ module Sequel
|
|
485
510
|
def index_definition_sql(table_name, index)
|
486
511
|
cols = index[:columns]
|
487
512
|
index_name = index[:name] || default_index_name(table_name, cols)
|
488
|
-
expr = if o = index[:opclass]
|
513
|
+
expr = if o = index[:opclass]
|
489
514
|
"(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
|
490
515
|
else
|
491
516
|
literal(Array(cols))
|
@@ -503,7 +528,7 @@ module Sequel
|
|
503
528
|
end
|
504
529
|
"CREATE #{unique}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
|
505
530
|
end
|
506
|
-
|
531
|
+
|
507
532
|
# The result of the insert for the given table and values. If values
|
508
533
|
# is an array, assume the first column is the primary key and return
|
509
534
|
# that. If values is a hash, lookup the primary key for the table. If
|
@@ -533,13 +558,13 @@ module Sequel
|
|
533
558
|
end
|
534
559
|
|
535
560
|
# Don't log, since logging is done by the underlying connection.
|
536
|
-
def log_connection_execute(conn, sql)
|
561
|
+
def log_connection_execute(conn, sql)
|
537
562
|
conn.execute(sql)
|
538
563
|
end
|
539
|
-
|
564
|
+
|
540
565
|
# Backbone of the tables and views support.
|
541
566
|
def pg_class_relname(type, opts)
|
542
|
-
ds = metadata_dataset.from(:pg_class).filter(:relkind=>type).select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
|
567
|
+
ds = metadata_dataset.from(:pg_class).filter(:relkind=>type).select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
|
543
568
|
ds = filter_schema(ds, opts)
|
544
569
|
m = output_identifier_meth
|
545
570
|
block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
|
@@ -550,7 +575,7 @@ module Sequel
|
|
550
575
|
def prepared_arg_placeholder
|
551
576
|
PREPARED_ARG_PLACEHOLDER
|
552
577
|
end
|
553
|
-
|
578
|
+
|
554
579
|
# Remove the cached entries for primary keys and sequences when a table is
|
555
580
|
# changed.
|
556
581
|
def remove_cached_schema(table)
|
@@ -564,7 +589,7 @@ module Sequel
|
|
564
589
|
# a rename table operation, so speciying a new schema in new_name will not have an effect.
|
565
590
|
def rename_table_sql(name, new_name)
|
566
591
|
"ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
|
567
|
-
end
|
592
|
+
end
|
568
593
|
|
569
594
|
# PostgreSQL's autoincrementing primary keys are of type integer or bigint
|
570
595
|
# using a nextval function call as a default.
|
@@ -613,7 +638,7 @@ module Sequel
|
|
613
638
|
def sql_function_args(args)
|
614
639
|
"(#{Array(args).map{|a| Array(a).reverse.join(' ')}.join(', ')})"
|
615
640
|
end
|
616
|
-
|
641
|
+
|
617
642
|
# Handle bigserial type if :serial option is present
|
618
643
|
def type_literal_generic_bignum(column)
|
619
644
|
column[:serial] ? :bigserial : super
|
@@ -643,7 +668,7 @@ module Sequel
|
|
643
668
|
end
|
644
669
|
end
|
645
670
|
end
|
646
|
-
|
671
|
+
|
647
672
|
# Instance methods for datasets that connect to a PostgreSQL database.
|
648
673
|
module DatasetMethods
|
649
674
|
ACCESS_SHARE = 'ACCESS SHARE'.freeze
|
@@ -688,7 +713,7 @@ module Sequel
|
|
688
713
|
BLOB_RE = /[\000-\037\047\134\177-\377]/n.freeze
|
689
714
|
WINDOW = " WINDOW ".freeze
|
690
715
|
EMPTY_STRING = ''.freeze
|
691
|
-
|
716
|
+
|
692
717
|
# Shared methods for prepared statements when used with PostgreSQL databases.
|
693
718
|
module PreparedStatementMethods
|
694
719
|
# Override insert action to use RETURNING if the server supports it.
|
@@ -722,7 +747,7 @@ module Sequel
|
|
722
747
|
def analyze
|
723
748
|
explain(:analyze=>true)
|
724
749
|
end
|
725
|
-
|
750
|
+
|
726
751
|
# Handle converting the ruby xor operator (^) into the
|
727
752
|
# PostgreSQL xor operator (#).
|
728
753
|
def complex_expression_sql_append(sql, op, args)
|
@@ -749,12 +774,12 @@ module Sequel
|
|
749
774
|
def explain(opts={})
|
750
775
|
with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(CRLF)
|
751
776
|
end
|
752
|
-
|
777
|
+
|
753
778
|
# Return a cloned dataset which will use FOR SHARE to lock returned rows.
|
754
779
|
def for_share
|
755
780
|
lock_style(:share)
|
756
781
|
end
|
757
|
-
|
782
|
+
|
758
783
|
# PostgreSQL specific full text search syntax, using tsearch2 (included
|
759
784
|
# in 8.3 by default, and available for earlier versions as an add-on).
|
760
785
|
def full_text_search(cols, terms, opts = {})
|
@@ -762,7 +787,7 @@ module Sequel
|
|
762
787
|
terms = terms.join(' | ') if terms.is_a?(Array)
|
763
788
|
filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, full_text_string_join(cols), lang, terms)
|
764
789
|
end
|
765
|
-
|
790
|
+
|
766
791
|
# Insert given values into the database.
|
767
792
|
def insert(*values, &block)
|
768
793
|
if @opts[:returning]
|
@@ -802,17 +827,17 @@ module Sequel
|
|
802
827
|
end
|
803
828
|
nil
|
804
829
|
end
|
805
|
-
|
830
|
+
|
806
831
|
# For PostgreSQL version > 8.2, allow inserting multiple rows at once.
|
807
832
|
def multi_insert_sql(columns, values)
|
808
833
|
return super if server_version < 80200
|
809
|
-
|
834
|
+
|
810
835
|
# postgresql 8.2 introduces support for multi-row insert
|
811
836
|
sql = LiteralString.new('VALUES ')
|
812
837
|
expression_list_append(sql, values.map{|r| Array(r)})
|
813
838
|
[insert_sql(columns, sql)]
|
814
839
|
end
|
815
|
-
|
840
|
+
|
816
841
|
# PostgreSQL supports using the WITH clause in subqueries if it
|
817
842
|
# supports using WITH at all (i.e. on PostgreSQL 8.4+).
|
818
843
|
def supports_cte_in_subqueries?
|
@@ -823,7 +848,7 @@ module Sequel
|
|
823
848
|
def supports_distinct_on?
|
824
849
|
true
|
825
850
|
end
|
826
|
-
|
851
|
+
|
827
852
|
# PostgreSQL supports modifying joined datasets
|
828
853
|
def supports_modifying_joins?
|
829
854
|
true
|
@@ -841,7 +866,7 @@ module Sequel
|
|
841
866
|
def supports_timestamp_timezones?
|
842
867
|
true
|
843
868
|
end
|
844
|
-
|
869
|
+
|
845
870
|
# PostgreSQL 8.4+ supports window functions
|
846
871
|
def supports_window_functions?
|
847
872
|
server_version >= 80400
|
@@ -851,7 +876,7 @@ module Sequel
|
|
851
876
|
def window(name, opts)
|
852
877
|
clone(:window=>(@opts[:window]||[]) + [[name, SQL::Window.new(opts)]])
|
853
878
|
end
|
854
|
-
|
879
|
+
|
855
880
|
protected
|
856
881
|
|
857
882
|
# If returned primary keys are requested, use RETURNING unless already set on the
|
@@ -876,11 +901,11 @@ module Sequel
|
|
876
901
|
end
|
877
902
|
|
878
903
|
private
|
879
|
-
|
904
|
+
|
880
905
|
# PostgreSQL allows deleting from joined datasets
|
881
906
|
def delete_clause_methods
|
882
907
|
server_version >= 90100 ? DELETE_CLAUSE_METHODS_91 : DELETE_CLAUSE_METHODS
|
883
|
-
end
|
908
|
+
end
|
884
909
|
|
885
910
|
# Only include the primary table in the main delete clause
|
886
911
|
def delete_from_sql(sql)
|
@@ -931,6 +956,19 @@ module Sequel
|
|
931
956
|
def literal_false
|
932
957
|
BOOL_FALSE
|
933
958
|
end
|
959
|
+
|
960
|
+
# PostgreSQL quotes NaN and Infinity.
|
961
|
+
def literal_float(value)
|
962
|
+
if value.finite?
|
963
|
+
super
|
964
|
+
elsif value.nan?
|
965
|
+
"'NaN'"
|
966
|
+
elsif value.infinite? == 1
|
967
|
+
"'Infinity'"
|
968
|
+
else
|
969
|
+
"'-Infinity'"
|
970
|
+
end
|
971
|
+
end
|
934
972
|
|
935
973
|
# Assume that SQL standard quoting is on, per Sequel's defaults
|
936
974
|
def literal_string_append(sql, v)
|
@@ -946,7 +984,7 @@ module Sequel
|
|
946
984
|
def select_clause_methods
|
947
985
|
server_version >= 80400 ? SELECT_CLAUSE_METHODS_84 : SELECT_CLAUSE_METHODS
|
948
986
|
end
|
949
|
-
|
987
|
+
|
950
988
|
# PostgreSQL requires parentheses around compound datasets if they use
|
951
989
|
# CTEs, and using them in other places doesn't hurt.
|
952
990
|
def compound_dataset_sql_append(sql, ds)
|
@@ -967,7 +1005,7 @@ module Sequel
|
|
967
1005
|
c = false
|
968
1006
|
co = COMMA
|
969
1007
|
as = AS
|
970
|
-
ws.map do |name, window|
|
1008
|
+
ws.map do |name, window|
|
971
1009
|
sql << co if c
|
972
1010
|
literal_append(sql, name)
|
973
1011
|
sql << as
|
@@ -976,7 +1014,7 @@ module Sequel
|
|
976
1014
|
end
|
977
1015
|
end
|
978
1016
|
end
|
979
|
-
|
1017
|
+
|
980
1018
|
# Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
|
981
1019
|
def select_with_sql_base
|
982
1020
|
opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
|