sequel 3.33.0 → 3.34.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +140 -0
- data/Rakefile +7 -0
- data/bin/sequel +22 -2
- data/doc/dataset_basics.rdoc +1 -1
- data/doc/mass_assignment.rdoc +3 -1
- data/doc/querying.rdoc +28 -4
- data/doc/reflection.rdoc +23 -3
- data/doc/release_notes/3.34.0.txt +671 -0
- data/doc/schema_modification.rdoc +18 -2
- data/doc/virtual_rows.rdoc +49 -0
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/ibmdb.rb +9 -4
- data/lib/sequel/adapters/jdbc.rb +9 -4
- data/lib/sequel/adapters/jdbc/h2.rb +8 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
- data/lib/sequel/adapters/mock.rb +24 -3
- data/lib/sequel/adapters/mysql.rb +29 -50
- data/lib/sequel/adapters/mysql2.rb +13 -28
- data/lib/sequel/adapters/oracle.rb +8 -2
- data/lib/sequel/adapters/postgres.rb +115 -20
- data/lib/sequel/adapters/shared/db2.rb +1 -1
- data/lib/sequel/adapters/shared/mssql.rb +14 -3
- data/lib/sequel/adapters/shared/mysql.rb +59 -11
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +127 -30
- data/lib/sequel/adapters/shared/sqlite.rb +55 -38
- data/lib/sequel/adapters/sqlite.rb +9 -3
- data/lib/sequel/adapters/swift.rb +2 -2
- data/lib/sequel/adapters/swift/mysql.rb +0 -5
- data/lib/sequel/adapters/swift/postgres.rb +10 -0
- data/lib/sequel/ast_transformer.rb +4 -0
- data/lib/sequel/connection_pool.rb +8 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
- data/lib/sequel/connection_pool/single.rb +5 -0
- data/lib/sequel/connection_pool/threaded.rb +14 -0
- data/lib/sequel/core.rb +24 -3
- data/lib/sequel/database/connecting.rb +24 -14
- data/lib/sequel/database/dataset_defaults.rb +1 -0
- data/lib/sequel/database/misc.rb +16 -25
- data/lib/sequel/database/query.rb +20 -2
- data/lib/sequel/database/schema_generator.rb +2 -2
- data/lib/sequel/database/schema_methods.rb +120 -23
- data/lib/sequel/dataset/actions.rb +91 -18
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/prepared_statements.rb +6 -2
- data/lib/sequel/dataset/sql.rb +68 -51
- data/lib/sequel/extensions/_pretty_table.rb +79 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
- data/lib/sequel/extensions/migration.rb +4 -0
- data/lib/sequel/extensions/null_dataset.rb +90 -0
- data/lib/sequel/extensions/pg_array.rb +460 -0
- data/lib/sequel/extensions/pg_array_ops.rb +220 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
- data/lib/sequel/extensions/pg_hstore.rb +296 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
- data/lib/sequel/extensions/pretty_table.rb +5 -71
- data/lib/sequel/extensions/query_literals.rb +79 -0
- data/lib/sequel/extensions/schema_caching.rb +76 -0
- data/lib/sequel/extensions/schema_dumper.rb +227 -31
- data/lib/sequel/extensions/select_remove.rb +35 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -110
- data/lib/sequel/extensions/to_dot.rb +1 -1
- data/lib/sequel/model.rb +11 -2
- data/lib/sequel/model/associations.rb +35 -7
- data/lib/sequel/model/base.rb +159 -36
- data/lib/sequel/no_core_ext.rb +2 -0
- data/lib/sequel/plugins/caching.rb +25 -18
- data/lib/sequel/plugins/composition.rb +1 -1
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/identity_map.rb +11 -3
- data/lib/sequel/plugins/instance_filters.rb +10 -0
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +4 -3
- data/lib/sequel/plugins/prepared_statements.rb +3 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
- data/lib/sequel/plugins/schema.rb +7 -2
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/static_cache.rb +99 -0
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/sql.rb +417 -7
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/firebird_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +12 -15
- data/spec/adapters/mysql_spec.rb +81 -23
- data/spec/adapters/postgres_spec.rb +444 -77
- data/spec/adapters/spec_helper.rb +2 -0
- data/spec/adapters/sqlite_spec.rb +8 -8
- data/spec/core/connection_pool_spec.rb +85 -0
- data/spec/core/database_spec.rb +29 -5
- data/spec/core/dataset_spec.rb +171 -3
- data/spec/core/expression_filters_spec.rb +364 -0
- data/spec/core/mock_adapter_spec.rb +17 -3
- data/spec/core/schema_spec.rb +133 -0
- data/spec/extensions/association_dependencies_spec.rb +13 -13
- data/spec/extensions/caching_spec.rb +26 -3
- data/spec/extensions/class_table_inheritance_spec.rb +2 -2
- data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +5 -2
- data/spec/extensions/identity_map_spec.rb +17 -0
- data/spec/extensions/instance_filters_spec.rb +1 -1
- data/spec/extensions/lazy_attributes_spec.rb +2 -2
- data/spec/extensions/list_spec.rb +4 -4
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
- data/spec/extensions/migration_spec.rb +6 -2
- data/spec/extensions/nested_attributes_spec.rb +20 -0
- data/spec/extensions/null_dataset_spec.rb +85 -0
- data/spec/extensions/optimistic_locking_spec.rb +2 -2
- data/spec/extensions/pg_array_ops_spec.rb +105 -0
- data/spec/extensions/pg_array_spec.rb +196 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
- data/spec/extensions/pg_hstore_spec.rb +195 -0
- data/spec/extensions/pg_statement_cache_spec.rb +209 -0
- data/spec/extensions/prepared_statements_spec.rb +4 -0
- data/spec/extensions/pretty_table_spec.rb +6 -0
- data/spec/extensions/query_literals_spec.rb +168 -0
- data/spec/extensions/schema_caching_spec.rb +41 -0
- data/spec/extensions/schema_dumper_spec.rb +231 -11
- data/spec/extensions/schema_spec.rb +14 -2
- data/spec/extensions/select_remove_spec.rb +38 -0
- data/spec/extensions/sharding_spec.rb +6 -6
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +2 -1
- data/spec/extensions/sql_expr_spec.rb +28 -19
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/touch_spec.rb +1 -1
- data/spec/extensions/typecast_on_load_spec.rb +9 -1
- data/spec/integration/associations_test.rb +6 -6
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +89 -26
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +3 -3
- data/spec/integration/plugin_test.rb +85 -22
- data/spec/integration/prepared_statement_test.rb +28 -8
- data/spec/integration/schema_test.rb +78 -7
- data/spec/integration/spec_helper.rb +1 -0
- data/spec/integration/timezone_test.rb +1 -1
- data/spec/integration/transaction_test.rb +4 -6
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/associations_spec.rb +94 -8
- data/spec/model/base_spec.rb +4 -4
- data/spec/model/hooks_spec.rb +2 -2
- data/spec/model/model_spec.rb +19 -7
- data/spec/model/record_spec.rb +135 -58
- data/spec/model/spec_helper.rb +1 -0
- metadata +35 -7
@@ -70,6 +70,16 @@ module Sequel
|
|
70
70
|
conn
|
71
71
|
end
|
72
72
|
|
73
|
+
# Return the number of matched rows when executing a delete/update statement.
|
74
|
+
def execute_dui(sql, opts={})
|
75
|
+
execute(sql, opts){|c| return c.affected_rows}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return the last inserted id when executing an insert statement.
|
79
|
+
def execute_insert(sql, opts={})
|
80
|
+
execute(sql, opts){|c| return c.last_id}
|
81
|
+
end
|
82
|
+
|
73
83
|
# Return the version of the MySQL server two which we are connecting.
|
74
84
|
def server_version(server=nil)
|
75
85
|
@server_version ||= (synchronize(server){|conn| conn.server_info[:id]} || super)
|
@@ -82,7 +92,7 @@ module Sequel
|
|
82
92
|
# yield the connection if a block is given.
|
83
93
|
def _execute(conn, sql, opts)
|
84
94
|
begin
|
85
|
-
r = log_yield(sql){conn.query(sql, :symbolize_keys => true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
|
95
|
+
r = log_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql){conn.query(sql, :symbolize_keys => true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
|
86
96
|
if opts[:type] == :select
|
87
97
|
yield r if r
|
88
98
|
elsif block_given?
|
@@ -131,13 +141,8 @@ module Sequel
|
|
131
141
|
|
132
142
|
Database::DatasetClass = self
|
133
143
|
|
134
|
-
# Delete rows matching this dataset
|
135
|
-
def delete
|
136
|
-
execute_dui(delete_sql){|c| return c.affected_rows}
|
137
|
-
end
|
138
|
-
|
139
144
|
# Yield all rows matching this dataset.
|
140
|
-
def fetch_rows(sql
|
145
|
+
def fetch_rows(sql)
|
141
146
|
execute(sql) do |r|
|
142
147
|
if identifier_output_method
|
143
148
|
cols = r.fields
|
@@ -152,27 +157,12 @@ module Sequel
|
|
152
157
|
end
|
153
158
|
else
|
154
159
|
@columns = r.fields
|
155
|
-
r.each(:cast_booleans => db.convert_tinyint_to_bool
|
160
|
+
r.each(:cast_booleans => db.convert_tinyint_to_bool){|h| yield h}
|
156
161
|
end
|
157
162
|
end
|
158
163
|
self
|
159
164
|
end
|
160
165
|
|
161
|
-
# Insert a new value into this dataset
|
162
|
-
def insert(*values)
|
163
|
-
execute_dui(insert_sql(*values)){|c| return c.last_id}
|
164
|
-
end
|
165
|
-
|
166
|
-
# Replace (update or insert) the matching row.
|
167
|
-
def replace(*args)
|
168
|
-
execute_dui(replace_sql(*args)){|c| return c.last_id}
|
169
|
-
end
|
170
|
-
|
171
|
-
# Update the matching rows.
|
172
|
-
def update(values={})
|
173
|
-
execute_dui(update_sql(values)){|c| return c.affected_rows}
|
174
|
-
end
|
175
|
-
|
176
166
|
private
|
177
167
|
|
178
168
|
# Set the :type option to :select if it hasn't been set.
|
@@ -180,11 +170,6 @@ module Sequel
|
|
180
170
|
super(sql, {:type=>:select}.merge(opts), &block)
|
181
171
|
end
|
182
172
|
|
183
|
-
# Set the :type option to :dui if it hasn't been set.
|
184
|
-
def execute_dui(sql, opts={}, &block)
|
185
|
-
super(sql, {:type=>:dui}.merge(opts), &block)
|
186
|
-
end
|
187
|
-
|
188
173
|
# Handle correct quoting of strings using ::Mysql2::Client#escape.
|
189
174
|
def literal_string_append(sql, v)
|
190
175
|
sql << "'" << db.synchronize{|c| c.escape(v)} << "'"
|
@@ -136,11 +136,17 @@ module Sequel
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
unless cursor
|
139
|
-
cursor = log_yield("
|
139
|
+
cursor = log_yield("PREPARE #{name}: #{sql}"){conn.parse(sql)}
|
140
140
|
conn.prepared_statements[name] = [cursor, sql]
|
141
141
|
end
|
142
142
|
args = cursor_bind_params(conn, cursor, opts[:arguments])
|
143
|
-
|
143
|
+
log_sql = "EXECUTE #{name}"
|
144
|
+
if ps.log_sql
|
145
|
+
log_sql << " ("
|
146
|
+
log_sql << sql
|
147
|
+
log_sql << ")"
|
148
|
+
end
|
149
|
+
r = log_yield(log_sql, args){cursor.exec}
|
144
150
|
if block_given?
|
145
151
|
yield(cursor)
|
146
152
|
elsif type == :insert
|
@@ -111,13 +111,14 @@ module Sequel
|
|
111
111
|
def date(s) ::Date.new(*s.split("-").map{|x| x.to_i}) end
|
112
112
|
end.new
|
113
113
|
|
114
|
-
# Hash with type name symbols and callable values for converting PostgreSQL types.
|
114
|
+
# Hash with type name strings/symbols and callable values for converting PostgreSQL types.
|
115
115
|
# Non-builtin types that don't have fixed numbers should use this to register
|
116
116
|
# conversion procs.
|
117
|
-
PG_NAMED_TYPES = {}
|
117
|
+
PG_NAMED_TYPES = {} unless defined?(PG_NAMED_TYPES)
|
118
118
|
|
119
119
|
# Hash with integer keys and callable values for converting PostgreSQL types.
|
120
|
-
PG_TYPES = {}
|
120
|
+
PG_TYPES = {} unless defined?(PG_TYPES)
|
121
|
+
|
121
122
|
{
|
122
123
|
[16] => tt.method(:boolean),
|
123
124
|
[17] => tt.method(:bytea),
|
@@ -193,7 +194,8 @@ module Sequel
|
|
193
194
|
# Execute the given SQL with this connection. If a block is given,
|
194
195
|
# yield the results, otherwise, return the number of changed rows.
|
195
196
|
def execute(sql, args=nil)
|
196
|
-
|
197
|
+
args = args.map{|v| @db.bound_variable_arg(v, self)} if args
|
198
|
+
q = check_disconnect_errors{execute_query(sql, args)}
|
197
199
|
begin
|
198
200
|
block_given? ? yield(q) : q.cmd_tuples
|
199
201
|
ensure
|
@@ -202,6 +204,12 @@ module Sequel
|
|
202
204
|
end
|
203
205
|
|
204
206
|
private
|
207
|
+
|
208
|
+
# Return the PGResult object that is returned by executing the given
|
209
|
+
# sql and args.
|
210
|
+
def execute_query(sql, args)
|
211
|
+
@db.log_yield(sql, args){args ? async_exec(sql, args) : async_exec(sql)}
|
212
|
+
end
|
205
213
|
|
206
214
|
# Return the requested values for the given row.
|
207
215
|
def single_value(r)
|
@@ -213,21 +221,48 @@ module Sequel
|
|
213
221
|
# pg, postgres, or postgres-pr driver.
|
214
222
|
class Database < Sequel::Database
|
215
223
|
include Sequel::Postgres::DatabaseMethods
|
224
|
+
|
225
|
+
INFINITE_TIMESTAMP_STRINGS = ['infinity'.freeze, '-infinity'.freeze].freeze
|
226
|
+
INFINITE_DATETIME_VALUES = ([1.0/0.0, -1.0/0.0] + INFINITE_TIMESTAMP_STRINGS).freeze
|
216
227
|
|
217
228
|
set_adapter_scheme :postgres
|
218
229
|
|
219
230
|
# A hash of conversion procs, keyed by type integer (oid) and
|
220
231
|
# having callable values for the conversion proc for that type.
|
221
232
|
attr_reader :conversion_procs
|
233
|
+
|
234
|
+
# Whether infinite timestamps should be converted on retrieval. By default, no
|
235
|
+
# conversion is done, so an error is raised if you attempt to retrieve an infinite
|
236
|
+
# timestamp. You can set this to :nil to convert to nil, :string to leave
|
237
|
+
# as a string, or :float to convert to an infinite float.
|
238
|
+
attr_accessor :convert_infinite_timestamps
|
222
239
|
|
223
240
|
# Add the primary_keys and primary_key_sequences instance variables,
|
224
241
|
# so we can get the correct return values for inserted rows.
|
225
242
|
def initialize(*args)
|
226
243
|
super
|
244
|
+
@convert_infinite_timestamps = false
|
227
245
|
@primary_keys = {}
|
228
246
|
@primary_key_sequences = {}
|
229
247
|
end
|
230
248
|
|
249
|
+
# Convert given argument so that it can be used directly by pg. Currently, pg doesn't
|
250
|
+
# handle fractional seconds in Time/DateTime or blobs with "\0", and it won't ever
|
251
|
+
# handle Sequel::SQLTime values correctly. Only public for use by the adapter, shouldn't
|
252
|
+
# be used by external code.
|
253
|
+
def bound_variable_arg(arg, conn)
|
254
|
+
case arg
|
255
|
+
when Sequel::SQL::Blob
|
256
|
+
conn.escape_bytea(arg)
|
257
|
+
when Sequel::SQLTime
|
258
|
+
literal(arg)
|
259
|
+
when DateTime, Time
|
260
|
+
literal(arg)
|
261
|
+
else
|
262
|
+
arg
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
231
266
|
# Connects to the database. In addition to the standard database
|
232
267
|
# options, using the :encoding or :charset option changes the
|
233
268
|
# client encoding for the connection, :connect_timeout is a
|
@@ -402,9 +437,30 @@ module Sequel
|
|
402
437
|
end
|
403
438
|
end
|
404
439
|
end
|
440
|
+
|
441
|
+
# Reset the database's conversion procs, requires a server query if there
|
442
|
+
# any named types.
|
443
|
+
def reset_conversion_procs
|
444
|
+
synchronize{|conn| @conversion_procs = get_conversion_procs(conn)}
|
445
|
+
end
|
446
|
+
|
447
|
+
# If convert_infinite_timestamps is true and the value is infinite, return an appropriate
|
448
|
+
# value based on the convert_infinite_timestamps setting.
|
449
|
+
def to_application_timestamp(value)
|
450
|
+
if c = convert_infinite_timestamps
|
451
|
+
case value
|
452
|
+
when *INFINITE_TIMESTAMP_STRINGS
|
453
|
+
infinite_timestamp_value(value)
|
454
|
+
else
|
455
|
+
super
|
456
|
+
end
|
457
|
+
else
|
458
|
+
super
|
459
|
+
end
|
460
|
+
end
|
405
461
|
|
406
462
|
private
|
407
|
-
|
463
|
+
|
408
464
|
# Convert exceptions raised from the block into DatabaseErrors.
|
409
465
|
def check_database_errors
|
410
466
|
begin
|
@@ -434,8 +490,11 @@ module Sequel
|
|
434
490
|
ps = prepared_statements[name]
|
435
491
|
sql = ps.prepared_sql
|
436
492
|
ps_name = name.to_s
|
437
|
-
args = opts[:arguments]
|
438
493
|
synchronize(opts[:server]) do |conn|
|
494
|
+
if args = opts[:arguments]
|
495
|
+
args = args.map{|arg| bound_variable_arg(arg, conn)}
|
496
|
+
end
|
497
|
+
|
439
498
|
unless conn.prepared_statements[ps_name] == sql
|
440
499
|
if conn.prepared_statements.include?(ps_name)
|
441
500
|
conn.execute("DEALLOCATE #{ps_name}") unless conn.prepared_statements[ps_name] == sql
|
@@ -443,7 +502,15 @@ module Sequel
|
|
443
502
|
conn.prepared_statements[ps_name] = sql
|
444
503
|
conn.check_disconnect_errors{log_yield("PREPARE #{ps_name} AS #{sql}"){conn.prepare(ps_name, sql)}}
|
445
504
|
end
|
446
|
-
|
505
|
+
|
506
|
+
log_sql = "EXECUTE #{ps_name}"
|
507
|
+
if ps.log_sql
|
508
|
+
log_sql << " ("
|
509
|
+
log_sql << sql
|
510
|
+
log_sql << ")"
|
511
|
+
end
|
512
|
+
|
513
|
+
q = conn.check_disconnect_errors{log_yield(log_sql, args){conn.exec_prepared(ps_name, args)}}
|
447
514
|
if opts[:table] && opts[:values]
|
448
515
|
insert_result(conn, opts[:table], opts[:values])
|
449
516
|
else
|
@@ -456,20 +523,48 @@ module Sequel
|
|
456
523
|
end
|
457
524
|
end
|
458
525
|
|
459
|
-
# Return the conversion procs hash to use for this database
|
526
|
+
# Return the conversion procs hash to use for this database.
|
460
527
|
def get_conversion_procs(conn)
|
461
528
|
procs = PG_TYPES.dup
|
462
|
-
procs[1114] = method(:to_application_timestamp)
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
procs[res.getvalue(recnum, 0).to_i] ||= pr
|
529
|
+
procs[1184] = procs[1114] = method(:to_application_timestamp)
|
530
|
+
unless (pgnt = PG_NAMED_TYPES).empty?
|
531
|
+
conn.execute("SELECT oid, typname FROM pg_type where typtype = 'b' AND typname IN ('#{pgnt.keys.map{|type| conn.escape_string(type.to_s)}.join("', '")}')") do |res|
|
532
|
+
res.ntuples.times do |i|
|
533
|
+
procs[res.getvalue(i, 0).to_i] ||= pgnt[res.getvalue(i, 1).untaint.to_sym]
|
468
534
|
end
|
469
535
|
end
|
470
536
|
end
|
471
537
|
procs
|
472
538
|
end
|
539
|
+
|
540
|
+
# Return an appropriate value for the given infinite timestamp string.
|
541
|
+
def infinite_timestamp_value(value)
|
542
|
+
case convert_infinite_timestamps
|
543
|
+
when :nil
|
544
|
+
nil
|
545
|
+
when :string
|
546
|
+
value
|
547
|
+
else
|
548
|
+
value == 'infinity' ? PLUS_INFINITY : MINUS_INFINITY
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
|
553
|
+
# If the value is an infinite value (either an infinite float or a string returned by
|
554
|
+
# by PostgreSQL for an infinite timestamp), return it without converting it if
|
555
|
+
# convert_infinite_timestamps is set.
|
556
|
+
def typecast_value_datetime(value)
|
557
|
+
if convert_infinite_timestamps
|
558
|
+
case value
|
559
|
+
when *INFINITE_DATETIME_VALUES
|
560
|
+
value
|
561
|
+
else
|
562
|
+
super
|
563
|
+
end
|
564
|
+
else
|
565
|
+
super
|
566
|
+
end
|
567
|
+
end
|
473
568
|
end
|
474
569
|
|
475
570
|
# Dataset class for PostgreSQL datasets that use the pg, postgres, or
|
@@ -481,9 +576,9 @@ module Sequel
|
|
481
576
|
|
482
577
|
# Yield all rows returned by executing the given SQL and converting
|
483
578
|
# the types.
|
484
|
-
def fetch_rows(sql
|
485
|
-
return cursor_fetch_rows(sql
|
486
|
-
execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res)
|
579
|
+
def fetch_rows(sql)
|
580
|
+
return cursor_fetch_rows(sql){|h| yield h} if @opts[:cursor]
|
581
|
+
execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res)){|h| yield h}}
|
487
582
|
end
|
488
583
|
|
489
584
|
# Uses a cursor for fetching records, instead of fetching the entire result
|
@@ -630,7 +725,7 @@ module Sequel
|
|
630
725
|
private
|
631
726
|
|
632
727
|
# Use a cursor to fetch groups of records at a time, yielding them to the block.
|
633
|
-
def cursor_fetch_rows(sql
|
728
|
+
def cursor_fetch_rows(sql)
|
634
729
|
server_opts = {:server=>@opts[:server] || :read_only}
|
635
730
|
db.transaction(server_opts) do
|
636
731
|
begin
|
@@ -642,12 +737,12 @@ module Sequel
|
|
642
737
|
# Load columns only in the first fetch, so subsequent fetches are faster
|
643
738
|
execute(fetch_sql) do |res|
|
644
739
|
cols = fetch_rows_set_cols(res)
|
645
|
-
yield_hash_rows(res, cols
|
740
|
+
yield_hash_rows(res, cols){|h| yield h}
|
646
741
|
return if res.ntuples < rows_per_fetch
|
647
742
|
end
|
648
743
|
loop do
|
649
744
|
execute(fetch_sql) do |res|
|
650
|
-
yield_hash_rows(res, cols
|
745
|
+
yield_hash_rows(res, cols){|h| yield h}
|
651
746
|
return if res.ntuples < rows_per_fetch
|
652
747
|
end
|
653
748
|
end
|
@@ -455,7 +455,8 @@ module Sequel
|
|
455
455
|
mutation_method(:output, into, values)
|
456
456
|
end
|
457
457
|
|
458
|
-
# MSSQL uses [] to quote identifiers
|
458
|
+
# MSSQL uses [] to quote identifiers. MSSQL does not support
|
459
|
+
# escaping of ], so you cannot use that character in an identifier.
|
459
460
|
def quoted_identifier_append(sql, name)
|
460
461
|
sql << BRACKET_OPEN << name.to_s << BRACKET_CLOSE
|
461
462
|
end
|
@@ -546,10 +547,16 @@ module Sequel
|
|
546
547
|
|
547
548
|
private
|
548
549
|
|
550
|
+
# Whether we are using SQL Server 2005 or later.
|
549
551
|
def is_2005_or_later?
|
550
552
|
server_version >= 9000000
|
551
553
|
end
|
552
554
|
|
555
|
+
# Whether we are using SQL Server 2008 or later.
|
556
|
+
def is_2008_or_later?
|
557
|
+
server_version >= 10000000
|
558
|
+
end
|
559
|
+
|
553
560
|
# MSSQL supports the OUTPUT clause for DELETE statements.
|
554
561
|
# It also allows prepending a WITH clause.
|
555
562
|
def delete_clause_methods
|
@@ -630,7 +637,7 @@ module Sequel
|
|
630
637
|
def select_into_sql(sql)
|
631
638
|
if i = @opts[:into]
|
632
639
|
sql << INTO
|
633
|
-
|
640
|
+
identifier_append(sql, i)
|
634
641
|
end
|
635
642
|
end
|
636
643
|
|
@@ -669,7 +676,7 @@ module Sequel
|
|
669
676
|
column_list_append(sql, output[:select_list])
|
670
677
|
if into = output[:into]
|
671
678
|
sql << INTO
|
672
|
-
|
679
|
+
identifier_append(sql, into)
|
673
680
|
if column_list = output[:column_list]
|
674
681
|
sql << PAREN_SPACE_OPEN
|
675
682
|
source_list_append(sql, column_list)
|
@@ -691,6 +698,10 @@ module Sequel
|
|
691
698
|
sql << SPACE
|
692
699
|
source_list_append(sql, @opts[:from][0..0])
|
693
700
|
end
|
701
|
+
|
702
|
+
def uses_with_rollup?
|
703
|
+
!is_2008_or_later?
|
704
|
+
end
|
694
705
|
end
|
695
706
|
end
|
696
707
|
end
|
@@ -51,6 +51,31 @@ module Sequel
|
|
51
51
|
:mysql
|
52
52
|
end
|
53
53
|
|
54
|
+
# Use the Information Schema's KEY_COLUMN_USAGE table to get
|
55
|
+
# basic information on foreign key columns, but include the
|
56
|
+
# constraint name.
|
57
|
+
def foreign_key_list(table, opts={})
|
58
|
+
m = output_identifier_meth
|
59
|
+
im = input_identifier_meth
|
60
|
+
ds = metadata_dataset.
|
61
|
+
from(:INFORMATION_SCHEMA__KEY_COLUMN_USAGE).
|
62
|
+
where(:TABLE_NAME=>im.call(table)).
|
63
|
+
exclude(:CONSTRAINT_NAME=>'PRIMARY').
|
64
|
+
exclude(:REFERENCED_TABLE_NAME=>nil).
|
65
|
+
select(:CONSTRAINT_NAME___name, :COLUMN_NAME___column, :REFERENCED_TABLE_NAME___table, :REFERENCED_COLUMN_NAME___key)
|
66
|
+
|
67
|
+
h = {}
|
68
|
+
ds.each do |row|
|
69
|
+
if r = h[row[:name]]
|
70
|
+
r[:columns] << m.call(row[:column])
|
71
|
+
r[:key] << m.call(row[:key])
|
72
|
+
else
|
73
|
+
h[row[:name]] = {:name=>m.call(row[:name]), :columns=>[m.call(row[:column])], :table=>m.call(row[:table]), :key=>[m.call(row[:key])]}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
h.values
|
77
|
+
end
|
78
|
+
|
54
79
|
# Use SHOW INDEX FROM to get the index information for the
|
55
80
|
# table.
|
56
81
|
#
|
@@ -407,6 +432,8 @@ module Sequel
|
|
407
432
|
STRAIGHT_JOIN = 'STRAIGHT_JOIN'.freeze
|
408
433
|
NATURAL_LEFT_JOIN = 'NATURAL LEFT JOIN'.freeze
|
409
434
|
BACKTICK = '`'.freeze
|
435
|
+
BACKTICK_RE = /`/.freeze
|
436
|
+
DOUBLE_BACKTICK = '``'.freeze
|
410
437
|
EMPTY_COLUMNS = " ()".freeze
|
411
438
|
EMPTY_VALUES = " VALUES ()".freeze
|
412
439
|
IGNORE = " IGNORE".freeze
|
@@ -415,6 +442,10 @@ module Sequel
|
|
415
442
|
EQ_VALUES = '=VALUES('.freeze
|
416
443
|
EQ = '='.freeze
|
417
444
|
WITH_ROLLUP = ' WITH ROLLUP'.freeze
|
445
|
+
MATCH_AGAINST = ["(MATCH ".freeze, " AGAINST (".freeze, "))".freeze].freeze
|
446
|
+
MATCH_AGAINST_BOOLEAN = ["(MATCH ".freeze, " AGAINST (".freeze, " IN BOOLEAN MODE))".freeze].freeze
|
447
|
+
EXPLAIN = 'EXPLAIN '.freeze
|
448
|
+
EXPLAIN_EXTENDED = 'EXPLAIN EXTENDED '.freeze
|
418
449
|
|
419
450
|
# MySQL specific syntax for LIKE/REGEXP searches, as well as
|
420
451
|
# string concatenation.
|
@@ -466,6 +497,17 @@ module Sequel
|
|
466
497
|
clone(:calc_found_rows => true)
|
467
498
|
end
|
468
499
|
|
500
|
+
# Return the results of an EXPLAIN query as a string. Options:
|
501
|
+
# :extended :: Use EXPLAIN EXPTENDED instead of EXPLAIN if true.
|
502
|
+
def explain(opts={})
|
503
|
+
# Load the PrettyTable class, needed for explain output
|
504
|
+
Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
|
505
|
+
|
506
|
+
ds = db.send(:metadata_dataset).with_sql((opts[:extended] ? EXPLAIN_EXTENDED : EXPLAIN) + select_sql).naked
|
507
|
+
rows = ds.all
|
508
|
+
Sequel::PrettyTable.string(rows, ds.columns)
|
509
|
+
end
|
510
|
+
|
469
511
|
# Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
|
470
512
|
def for_share
|
471
513
|
lock_style(:share)
|
@@ -479,7 +521,7 @@ module Sequel
|
|
479
521
|
# MySQL specific full text search syntax.
|
480
522
|
def full_text_sql(cols, terms, opts = {})
|
481
523
|
terms = terms.join(' ') if terms.is_a?(Array)
|
482
|
-
SQL::PlaceholderLiteralString.new(
|
524
|
+
SQL::PlaceholderLiteralString.new((opts[:boolean] ? MATCH_AGAINST_BOOLEAN : MATCH_AGAINST), [Array(cols), terms])
|
483
525
|
end
|
484
526
|
|
485
527
|
# MySQL allows HAVING clause on ungrouped datasets.
|
@@ -552,14 +594,24 @@ module Sequel
|
|
552
594
|
|
553
595
|
# MySQL uses the nonstandard ` (backtick) for quoting identifiers.
|
554
596
|
def quoted_identifier_append(sql, c)
|
555
|
-
sql << BACKTICK << c.to_s << BACKTICK
|
597
|
+
sql << BACKTICK << c.to_s.gsub(BACKTICK_RE, DOUBLE_BACKTICK) << BACKTICK
|
556
598
|
end
|
557
599
|
|
600
|
+
# Execute a REPLACE statement on the database.
|
601
|
+
def replace(*values)
|
602
|
+
execute_insert(replace_sql(*values))
|
603
|
+
end
|
604
|
+
|
558
605
|
# MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
|
559
606
|
# insert if it doesn't).
|
560
607
|
def replace_sql(*values)
|
561
608
|
clone(:replace=>true).insert_sql(*values)
|
562
609
|
end
|
610
|
+
|
611
|
+
# Replace multiple rows in a single query.
|
612
|
+
def multi_replace(*values)
|
613
|
+
clone(:replace=>true).multi_insert(*values)
|
614
|
+
end
|
563
615
|
|
564
616
|
# MySQL can emulate DISTINCT ON with its non-standard GROUP BY implementation,
|
565
617
|
# though the rows returned cannot be made deterministic through ordering.
|
@@ -730,15 +782,6 @@ module Sequel
|
|
730
782
|
SELECT_CLAUSE_METHODS
|
731
783
|
end
|
732
784
|
|
733
|
-
# MySQL supports ROLLUP via nonstandard SQL syntax
|
734
|
-
def select_group_sql(sql)
|
735
|
-
if group = @opts[:group]
|
736
|
-
sql << GROUP_BY
|
737
|
-
expression_list_append(sql, group)
|
738
|
-
sql << WITH_ROLLUP if @opts[:group_options] == :rollup
|
739
|
-
end
|
740
|
-
end
|
741
|
-
|
742
785
|
# Support FOR SHARE locking when using the :share lock style.
|
743
786
|
def select_lock_sql(sql)
|
744
787
|
@opts[:lock] == :share ? (sql << FOR_SHARE) : super
|
@@ -753,6 +796,11 @@ module Sequel
|
|
753
796
|
def update_clause_methods
|
754
797
|
UPDATE_CLAUSE_METHODS
|
755
798
|
end
|
799
|
+
|
800
|
+
# MySQL uses WITH ROLLUP syntax.
|
801
|
+
def uses_with_rollup?
|
802
|
+
true
|
803
|
+
end
|
756
804
|
end
|
757
805
|
end
|
758
806
|
end
|