sequel 3.33.0 → 3.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|