sequel 3.27.0 → 3.28.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 +96 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/doc/association_basics.rdoc +48 -0
- data/doc/opening_databases.rdoc +29 -5
- data/doc/prepared_statements.rdoc +1 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/testing.rdoc +42 -0
- data/doc/transactions.rdoc +97 -0
- data/lib/sequel/adapters/db2.rb +95 -65
- data/lib/sequel/adapters/firebird.rb +25 -219
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/jdbc.rb +12 -0
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/mysql.rb +10 -15
- data/lib/sequel/adapters/odbc.rb +1 -2
- data/lib/sequel/adapters/odbc/db2.rb +5 -5
- data/lib/sequel/adapters/postgres.rb +71 -11
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/mssql.rb +18 -75
- data/lib/sequel/adapters/shared/mysql.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +52 -36
- data/lib/sequel/adapters/shared/sqlite.rb +32 -36
- data/lib/sequel/adapters/sqlite.rb +4 -8
- data/lib/sequel/adapters/tinytds.rb +7 -3
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -5
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -1
- data/lib/sequel/dataset/actions.rb +149 -33
- data/lib/sequel/dataset/features.rb +44 -7
- data/lib/sequel/dataset/misc.rb +9 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -2
- data/lib/sequel/dataset/query.rb +63 -10
- data/lib/sequel/dataset/sql.rb +22 -5
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +250 -27
- data/lib/sequel/model/base.rb +10 -16
- data/lib/sequel/plugins/many_through_many.rb +34 -2
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/sql.rb +94 -51
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/postgres_spec.rb +74 -6
- data/spec/adapters/spec_helper.rb +1 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/database_spec.rb +7 -0
- data/spec/core/dataset_spec.rb +180 -17
- data/spec/core/expression_filters_spec.rb +107 -41
- data/spec/core/spec_helper.rb +11 -0
- data/spec/extensions/many_through_many_spec.rb +115 -1
- data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
- data/spec/integration/associations_test.rb +193 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +215 -19
- data/spec/integration/plugin_test.rb +8 -5
- data/spec/integration/prepared_statement_test.rb +91 -98
- data/spec/integration/schema_test.rb +27 -11
- data/spec/integration/spec_helper.rb +10 -0
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +13 -0
- data/spec/model/base_spec.rb +8 -21
- data/spec/model/eager_loading_spec.rb +243 -9
- data/spec/model/model_spec.rb +15 -2
- metadata +16 -4
@@ -10,7 +10,7 @@ module Sequel
|
|
10
10
|
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'".freeze
|
11
11
|
TEMP_STORE = [:default, :file, :memory].freeze
|
12
12
|
VIEWS_FILTER = "type = 'view'".freeze
|
13
|
-
|
13
|
+
|
14
14
|
# Run all alter_table commands in a transaction. This is technically only
|
15
15
|
# needed for drop column.
|
16
16
|
def alter_table(name, generator=nil, &block)
|
@@ -107,6 +107,19 @@ module Sequel
|
|
107
107
|
sqlite_version >= 30608
|
108
108
|
end
|
109
109
|
|
110
|
+
# Override the default setting for whether to use timezones in timestamps.
|
111
|
+
# For backwards compatibility, it is set to +true+ by default.
|
112
|
+
# Anyone wanting to use SQLite's datetime functions should set it to +false+
|
113
|
+
# using this method. It's possible that the default will change in a future version,
|
114
|
+
# so anyone relying on timezones in timestamps should set this to +true+.
|
115
|
+
attr_writer :use_timestamp_timezones
|
116
|
+
|
117
|
+
# SQLite supports timezones in timestamps, since it just stores them as strings,
|
118
|
+
# but it breaks the usage of SQLite's datetime functions.
|
119
|
+
def use_timestamp_timezones?
|
120
|
+
defined?(@use_timestamp_timezones) ? @use_timestamp_timezones : (@use_timestamp_timezones = true)
|
121
|
+
end
|
122
|
+
|
110
123
|
# A symbol signifying the value of the synchronous PRAGMA.
|
111
124
|
def synchronous
|
112
125
|
SYNCHRONOUS[pragma_get(:synchronous).to_i]
|
@@ -339,23 +352,7 @@ module Sequel
|
|
339
352
|
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
|
340
353
|
COMMA_SEPARATOR = ', '.freeze
|
341
354
|
CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
|
342
|
-
|
343
|
-
# Ugly hack. Really, SQLite uses 0 for false and 1 for true
|
344
|
-
# but then you can't differentiate between integers and booleans.
|
345
|
-
# In filters, SQL::BooleanConstants are used more, while in other places
|
346
|
-
# the ruby true/false values are used more, so use 1/0 for SQL::BooleanConstants.
|
347
|
-
# The correct fix for this would require separate literalization paths for
|
348
|
-
# filters compared to other values, but that's more work than I want to do right now.
|
349
|
-
def boolean_constant_sql(constant)
|
350
|
-
case constant
|
351
|
-
when true
|
352
|
-
'1'
|
353
|
-
when false
|
354
|
-
'0'
|
355
|
-
else
|
356
|
-
super
|
357
|
-
end
|
358
|
-
end
|
355
|
+
EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
|
359
356
|
|
360
357
|
# SQLite does not support pattern matching via regular expressions.
|
361
358
|
# SQLite is case insensitive (depending on pragma), so use LIKE for
|
@@ -367,6 +364,15 @@ module Sequel
|
|
367
364
|
when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
368
365
|
# SQLite is case insensitive for ASCII, and non case sensitive for other character sets
|
369
366
|
"#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
|
367
|
+
when :^
|
368
|
+
a = literal(args.at(0))
|
369
|
+
b = literal(args.at(1))
|
370
|
+
"((~(#{a} & #{b})) & (#{a} | #{b}))"
|
371
|
+
when :extract
|
372
|
+
part = args.at(0)
|
373
|
+
raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
|
374
|
+
expr = args.at(1)
|
375
|
+
"CAST(strftime(#{format}, #{literal(expr)}) AS #{part == :second ? 'NUMERIC' : 'INTEGER'})"
|
370
376
|
else
|
371
377
|
super(op, args)
|
372
378
|
end
|
@@ -418,11 +424,17 @@ module Sequel
|
|
418
424
|
end
|
419
425
|
|
420
426
|
# SQLite supports timezones in literal timestamps, since it stores them
|
421
|
-
# as text.
|
427
|
+
# as text. But using timezones in timestamps breaks SQLite datetime
|
428
|
+
# functions, so we allow the user to override the default per database.
|
422
429
|
def supports_timestamp_timezones?
|
423
|
-
|
430
|
+
db.use_timestamp_timezones?
|
424
431
|
end
|
425
432
|
|
433
|
+
# SQLite cannot use WHERE 't'.
|
434
|
+
def supports_where_true?
|
435
|
+
false
|
436
|
+
end
|
437
|
+
|
426
438
|
private
|
427
439
|
|
428
440
|
# SQLite uses string literals instead of identifiers in AS clauses.
|
@@ -431,22 +443,6 @@ module Sequel
|
|
431
443
|
"#{expression} AS #{literal(aliaz.to_s)}"
|
432
444
|
end
|
433
445
|
|
434
|
-
# Special case when true or false is provided directly to filter.
|
435
|
-
def filter_expr(expr)
|
436
|
-
if block_given?
|
437
|
-
super
|
438
|
-
else
|
439
|
-
case expr
|
440
|
-
when true
|
441
|
-
1
|
442
|
-
when false
|
443
|
-
0
|
444
|
-
else
|
445
|
-
super
|
446
|
-
end
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
446
|
# SQL fragment specifying a list of identifiers
|
451
447
|
def identifier_list(columns)
|
452
448
|
columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
|
@@ -13,26 +13,22 @@ module Sequel
|
|
13
13
|
TYPE_TRANSLATOR = tt = Class.new do
|
14
14
|
FALSE_VALUES = %w'0 false f no n'.freeze
|
15
15
|
def boolean(s) !FALSE_VALUES.include?(s.downcase) end
|
16
|
-
def blob(s) ::Sequel::SQL::Blob.new(s) end
|
17
16
|
def integer(s) s.to_i end
|
18
17
|
def float(s) s.to_f end
|
19
18
|
def numeric(s) ::BigDecimal.new(s) rescue s end
|
20
|
-
def date(s) ::Sequel.string_to_date(s) end
|
21
|
-
def time(s) ::Sequel.string_to_time(s) end
|
22
|
-
def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
|
23
19
|
end.new
|
24
20
|
|
25
21
|
# Hash with string keys and callable values for converting SQLite types.
|
26
22
|
SQLITE_TYPES = {}
|
27
23
|
{
|
28
|
-
%w'timestamp datetime' =>
|
29
|
-
%w'date' =>
|
30
|
-
%w'time' =>
|
24
|
+
%w'timestamp datetime' => ::Sequel.method(:database_to_application_timestamp),
|
25
|
+
%w'date' => ::Sequel.method(:string_to_date),
|
26
|
+
%w'time' => ::Sequel.method(:string_to_time),
|
31
27
|
%w'bit bool boolean' => tt.method(:boolean),
|
32
28
|
%w'integer smallint mediumint int bigint' => tt.method(:integer),
|
33
29
|
%w'numeric decimal money' => tt.method(:numeric),
|
34
30
|
%w'float double real dec fixed' + ['double precision'] => tt.method(:float),
|
35
|
-
%w'blob' =>
|
31
|
+
%w'blob' => ::Sequel::SQL::Blob.method(:new)
|
36
32
|
}.each do |k,v|
|
37
33
|
k.each{|n| SQLITE_TYPES[n] = v}
|
38
34
|
end
|
@@ -124,9 +124,13 @@ module Sequel
|
|
124
124
|
[v, 'double precision']
|
125
125
|
when Numeric
|
126
126
|
[v, 'numeric']
|
127
|
-
when
|
128
|
-
|
129
|
-
|
127
|
+
when Time
|
128
|
+
if v.is_a?(SQLTime)
|
129
|
+
[literal(v), 'time']
|
130
|
+
else
|
131
|
+
[literal(v), 'datetime']
|
132
|
+
end
|
133
|
+
when DateTime
|
130
134
|
[literal(v), 'datetime']
|
131
135
|
when Date
|
132
136
|
[literal(v), 'date']
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Sequel
|
2
|
+
module EmulateOffsetWithRowNumber
|
3
|
+
# When a subselect that uses :offset is used in IN or NOT IN,
|
4
|
+
# use a nested subselect that only includes the first column
|
5
|
+
# instead of the ROW_NUMBER column added by the emulated offset support.
|
6
|
+
def complex_expression_sql(op, args)
|
7
|
+
case op
|
8
|
+
when :IN, :"NOT IN"
|
9
|
+
ds = args.at(1)
|
10
|
+
if ds.is_a?(Sequel::Dataset) && ds.opts[:offset]
|
11
|
+
c = ds.opts[:select].first
|
12
|
+
case c
|
13
|
+
when Symbol
|
14
|
+
t, cl, a = split_symbol(c)
|
15
|
+
if a
|
16
|
+
c = SQL::Identifier.new(a)
|
17
|
+
elsif t
|
18
|
+
c = SQL::Identifier.new(cl)
|
19
|
+
end
|
20
|
+
when SQL::AliasedExpression
|
21
|
+
c = SQL::Identifier.new(c.aliaz)
|
22
|
+
when SQL::QualifiedIdentifier
|
23
|
+
c = SQL::Identifier.new(c.column)
|
24
|
+
end
|
25
|
+
super(op, [args.at(0), ds.from_self.select(c)])
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Emulate OFFSET support with the ROW_NUMBER window function
|
35
|
+
#
|
36
|
+
# The implementation is ugly, cloning the current dataset and modifying
|
37
|
+
# the clone to add a ROW_NUMBER window function (and some other things),
|
38
|
+
# then using the modified clone in a subselect which is selected from.
|
39
|
+
#
|
40
|
+
# If offset is used, an order must be provided, because the use of ROW_NUMBER
|
41
|
+
# requires an order.
|
42
|
+
def select_sql
|
43
|
+
return super unless o = @opts[:offset]
|
44
|
+
raise(Error, "#{db.database_type} requires an order be provided if using an offset") unless order = @opts[:order]
|
45
|
+
dsa1 = dataset_alias(1)
|
46
|
+
rn = row_number_column
|
47
|
+
subselect_sql(unlimited.
|
48
|
+
unordered.
|
49
|
+
select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
|
50
|
+
from_self(:alias=>dsa1).
|
51
|
+
limit(@opts[:limit]).
|
52
|
+
where(SQL::Identifier.new(rn) > o))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/sequel/core.rb
CHANGED
@@ -188,7 +188,7 @@ module Sequel
|
|
188
188
|
# This is used to ensure that the files loaded are from the same version of
|
189
189
|
# Sequel as this file.
|
190
190
|
def self.require(files, subdir=nil)
|
191
|
-
Array(files).each{|f| super("#{File.dirname(__FILE__)}/#{"#{subdir}/" if subdir}#{f}")}
|
191
|
+
Array(files).each{|f| super("#{File.dirname(__FILE__).untaint}/#{"#{subdir}/" if subdir}#{f}")}
|
192
192
|
end
|
193
193
|
|
194
194
|
# Set whether to set the single threaded mode for all databases by default. By default,
|
@@ -6,7 +6,7 @@ module Sequel
|
|
6
6
|
# ---------------------
|
7
7
|
|
8
8
|
# Array of supported database adapters
|
9
|
-
ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
|
9
|
+
ADAPTERS = %w'ado amalgalite db2 dbi do firebird ibmdb informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
|
10
10
|
|
11
11
|
# Whether to use the single threaded connection pool by default
|
12
12
|
@@single_threaded = false
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -275,15 +275,16 @@ module Sequel
|
|
275
275
|
# Typecast the value to a Time
|
276
276
|
def typecast_value_time(value)
|
277
277
|
case value
|
278
|
-
when SQLTime
|
279
|
-
value
|
280
278
|
when Time
|
281
|
-
|
279
|
+
if value.is_a?(SQLTime)
|
280
|
+
value
|
281
|
+
else
|
282
|
+
SQLTime.create(value.hour, value.min, value.sec, value.respond_to?(:nsec) ? value.nsec/1000.0 : value.usec)
|
283
|
+
end
|
282
284
|
when String
|
283
285
|
Sequel.string_to_time(value)
|
284
286
|
when Hash
|
285
|
-
|
286
|
-
SQLTime.local(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
287
|
+
SQLTime.create(*[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
287
288
|
else
|
288
289
|
raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
|
289
290
|
end
|
@@ -471,7 +471,7 @@ module Sequel
|
|
471
471
|
:float
|
472
472
|
when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
|
473
473
|
$1 && $1 == '0' ? :integer : :decimal
|
474
|
-
when /bytea|
|
474
|
+
when /bytea|[bc]lob|image|(var)?binary/io
|
475
475
|
:blob
|
476
476
|
when /\Aenum/io
|
477
477
|
:enum
|
@@ -86,7 +86,8 @@ module Sequel
|
|
86
86
|
# :index :: Create an index on this column.
|
87
87
|
# :key :: For foreign key columns, the column in the associated table
|
88
88
|
# that this column references. Unnecessary if this column
|
89
|
-
# references the primary key of the associated table
|
89
|
+
# references the primary key of the associated table, except if you are
|
90
|
+
# using MySQL.
|
90
91
|
# :null :: Mark the column as allowing NULL values (if true),
|
91
92
|
# or not allowing NULL values (if false). If unspecified, will default
|
92
93
|
# to whatever the database default is.
|
@@ -101,10 +101,15 @@ module Sequel
|
|
101
101
|
#
|
102
102
|
# DB[:table].delete # DELETE * FROM table
|
103
103
|
# # => 3
|
104
|
-
def delete
|
105
|
-
|
104
|
+
def delete(&block)
|
105
|
+
sql = delete_sql
|
106
|
+
if uses_returning?(:delete)
|
107
|
+
returning_fetch_rows(sql, &block)
|
108
|
+
else
|
109
|
+
execute_dui(sql)
|
110
|
+
end
|
106
111
|
end
|
107
|
-
|
112
|
+
|
108
113
|
# Iterates over the records in the dataset as they are yielded from the
|
109
114
|
# database adapter, and returns self.
|
110
115
|
#
|
@@ -279,8 +284,13 @@ module Sequel
|
|
279
284
|
#
|
280
285
|
# DB[:items].insert([:a, :b], DB[:old_items])
|
281
286
|
# # INSERT INTO items (a, b) SELECT * FROM old_items
|
282
|
-
def insert(*values)
|
283
|
-
|
287
|
+
def insert(*values, &block)
|
288
|
+
sql = insert_sql(*values)
|
289
|
+
if uses_returning?(:insert)
|
290
|
+
returning_fetch_rows(sql, &block)
|
291
|
+
else
|
292
|
+
execute_insert(sql)
|
293
|
+
end
|
284
294
|
end
|
285
295
|
|
286
296
|
# Inserts multiple values. If a block is given it is invoked for each
|
@@ -336,10 +346,19 @@ module Sequel
|
|
336
346
|
#
|
337
347
|
# DB[:table].map{|r| r[:id] * 2} # SELECT * FROM table
|
338
348
|
# # => [2, 4, 6, ...]
|
349
|
+
#
|
350
|
+
# You can also provide an array of column names:
|
351
|
+
#
|
352
|
+
# DB[:table].map([:id, :name]) # SELECT * FROM table
|
353
|
+
# # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
|
339
354
|
def map(column=nil, &block)
|
340
355
|
if column
|
341
356
|
raise(Error, ARG_BLOCK_ERROR_MSG) if block
|
342
|
-
|
357
|
+
if column.is_a?(Array)
|
358
|
+
super(){|r| column.map{|c| r[c]}}
|
359
|
+
else
|
360
|
+
super(){|r| r[column]}
|
361
|
+
end
|
343
362
|
else
|
344
363
|
super(&block)
|
345
364
|
end
|
@@ -395,8 +414,24 @@ module Sequel
|
|
395
414
|
#
|
396
415
|
# DB[:table].select_hash(:id, :name) # SELECT id, name FROM table
|
397
416
|
# # => {1=>'a', 2=>'b', ...}
|
417
|
+
#
|
418
|
+
# You can also provide an array of column names for either the key_column,
|
419
|
+
# the value column, or both:
|
420
|
+
#
|
421
|
+
# DB[:table].select_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
|
422
|
+
# # {[1, 3]=>['a', 'c'], [2, 4]=>['b', 'd'], ...}
|
398
423
|
def select_hash(key_column, value_column)
|
399
|
-
|
424
|
+
if key_column.is_a?(Array)
|
425
|
+
if value_column.is_a?(Array)
|
426
|
+
select(*(key_column + value_column)).to_hash(key_column.map{|c| hash_key_symbol(c)}, value_column.map{|c| hash_key_symbol(c)})
|
427
|
+
else
|
428
|
+
select(*(key_column + [value_column])).to_hash(key_column.map{|c| hash_key_symbol(c)}, hash_key_symbol(value_column))
|
429
|
+
end
|
430
|
+
elsif value_column.is_a?(Array)
|
431
|
+
select(key_column, *value_column).to_hash(hash_key_symbol(key_column), value_column.map{|c| hash_key_symbol(c)})
|
432
|
+
else
|
433
|
+
select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
|
434
|
+
end
|
400
435
|
end
|
401
436
|
|
402
437
|
# Selects the column given (either as an argument or as a block), and
|
@@ -410,35 +445,32 @@ module Sequel
|
|
410
445
|
#
|
411
446
|
# DB[:table].select_map{id * 2} # SELECT (id * 2) FROM table
|
412
447
|
# # => [6, 10, 16, 2, ...]
|
448
|
+
#
|
449
|
+
# You can also provide an array of column names:
|
450
|
+
#
|
451
|
+
# DB[:table].select_map([:id, :name]) # SELECT id, name FROM table
|
452
|
+
# # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
|
413
453
|
def select_map(column=nil, &block)
|
414
|
-
|
415
|
-
ds = if column
|
416
|
-
raise(Error, ARG_BLOCK_ERROR_MSG) if block
|
417
|
-
ds.select(column)
|
418
|
-
else
|
419
|
-
ds.select(&block)
|
420
|
-
end
|
421
|
-
ds.map{|r| r.values.first}
|
454
|
+
_select_map(column, false, &block)
|
422
455
|
end
|
456
|
+
|
423
457
|
|
424
458
|
# The same as select_map, but in addition orders the array by the column.
|
425
459
|
#
|
426
460
|
# DB[:table].select_order_map(:id) # SELECT id FROM table ORDER BY id
|
427
461
|
# # => [1, 2, 3, 4, ...]
|
428
462
|
#
|
429
|
-
# DB[:table].select_order_map{
|
463
|
+
# DB[:table].select_order_map{id * 2} # SELECT (id * 2) FROM table ORDER BY (id * 2)
|
430
464
|
# # => [2, 4, 6, 8, ...]
|
465
|
+
#
|
466
|
+
# You can also provide an array of column names:
|
467
|
+
#
|
468
|
+
# DB[:table].select_order_map([:id, :name]) # SELECT id, name FROM table ORDER BY id, name
|
469
|
+
# # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
|
431
470
|
def select_order_map(column=nil, &block)
|
432
|
-
|
433
|
-
ds = if column
|
434
|
-
raise(Error, ARG_BLOCK_ERROR_MSG) if block
|
435
|
-
ds.select(column).order(unaliased_identifier(column))
|
436
|
-
else
|
437
|
-
ds.select(&block).order(&block)
|
438
|
-
end
|
439
|
-
ds.map{|r| r.values.first}
|
471
|
+
_select_map(column, true, &block)
|
440
472
|
end
|
441
|
-
|
473
|
+
|
442
474
|
# Alias for update, but not aliased directly so subclasses
|
443
475
|
# don't have to override both methods.
|
444
476
|
def set(*args)
|
@@ -502,11 +534,37 @@ module Sequel
|
|
502
534
|
#
|
503
535
|
# DB[:table].to_hash(:id) # SELECT * FROM table
|
504
536
|
# # {1=>{:id=>1, :name=>'Jim'}, 2=>{:id=>2, :name=>'Bob'}, ...}
|
537
|
+
#
|
538
|
+
# You can also provide an array of column names for either the key_column,
|
539
|
+
# the value column, or both:
|
540
|
+
#
|
541
|
+
# DB[:table].to_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
|
542
|
+
# # {[1, 3]=>['Jim', 'bo'], [2, 4]=>['Bob', 'be'], ...}
|
543
|
+
#
|
544
|
+
# DB[:table].to_hash([:id, :name]) # SELECT * FROM table
|
545
|
+
# # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob'=>{:id=>2, :name=>'Bob'}, ...}
|
505
546
|
def to_hash(key_column, value_column = nil)
|
506
|
-
|
507
|
-
|
508
|
-
|
547
|
+
h = {}
|
548
|
+
if value_column
|
549
|
+
if value_column.is_a?(Array)
|
550
|
+
if key_column.is_a?(Array)
|
551
|
+
each{|r| h[key_column.map{|c| r[c]}] = value_column.map{|c| r[c]}}
|
552
|
+
else
|
553
|
+
each{|r| h[r[key_column]] = value_column.map{|c| r[c]}}
|
554
|
+
end
|
555
|
+
else
|
556
|
+
if key_column.is_a?(Array)
|
557
|
+
each{|r| h[key_column.map{|c| r[c]}] = r[value_column]}
|
558
|
+
else
|
559
|
+
each{|r| h[r[key_column]] = r[value_column]}
|
560
|
+
end
|
561
|
+
end
|
562
|
+
elsif key_column.is_a?(Array)
|
563
|
+
each{|r| h[key_column.map{|c| r[c]}] = r}
|
564
|
+
else
|
565
|
+
each{|r| h[r[key_column]] = r}
|
509
566
|
end
|
567
|
+
h
|
510
568
|
end
|
511
569
|
|
512
570
|
# Truncates the dataset. Returns nil.
|
@@ -527,12 +585,38 @@ module Sequel
|
|
527
585
|
#
|
528
586
|
# DB[:table].update(:x=>:x+1, :y=>0) # UPDATE table SET x = (x + 1), y = 0
|
529
587
|
# # => 10
|
530
|
-
def update(values={})
|
531
|
-
|
588
|
+
def update(values={}, &block)
|
589
|
+
sql = update_sql(values)
|
590
|
+
if uses_returning?(:update)
|
591
|
+
returning_fetch_rows(sql, &block)
|
592
|
+
else
|
593
|
+
execute_dui(sql)
|
594
|
+
end
|
532
595
|
end
|
533
596
|
|
534
597
|
private
|
535
598
|
|
599
|
+
# Internals of +select_map+ and +select_order_map+
|
600
|
+
def _select_map(column, order, &block)
|
601
|
+
ds = naked.ungraphed
|
602
|
+
if column
|
603
|
+
raise(Error, ARG_BLOCK_ERROR_MSG) if block
|
604
|
+
columns = Array(column)
|
605
|
+
select_cols = order ? columns.map{|c| c.is_a?(SQL::OrderedExpression) ? c.expression : c} : columns
|
606
|
+
ds = ds.select(*select_cols)
|
607
|
+
ds = ds.order(*columns.map{|c| unaliased_identifier(c)}) if order
|
608
|
+
else
|
609
|
+
ds = ds.select(&block)
|
610
|
+
ds = ds.order(&block) if order
|
611
|
+
end
|
612
|
+
if ds.opts[:select].length > 1
|
613
|
+
ret_cols = select_cols.map{|c| hash_key_symbol(c)}
|
614
|
+
ds.map{|r| ret_cols.map{|c| r[c]}}
|
615
|
+
else
|
616
|
+
ds.map{|r| r.values.first}
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
536
620
|
# Set the server to use to :default unless it is already set in the passed opts
|
537
621
|
def default_server_opts(opts)
|
538
622
|
{:server=>@opts[:server] || :default}.merge(opts)
|
@@ -564,9 +648,19 @@ module Sequel
|
|
564
648
|
# specifying the symbol that is likely to be used as the hash key
|
565
649
|
# for the column when records are returned.
|
566
650
|
def hash_key_symbol(s)
|
567
|
-
|
568
|
-
|
569
|
-
|
651
|
+
case s
|
652
|
+
when Symbol
|
653
|
+
_, c, a = split_symbol(s)
|
654
|
+
(a || c).to_sym
|
655
|
+
when SQL::Identifier
|
656
|
+
hash_key_symbol(s.value)
|
657
|
+
when SQL::QualifiedIdentifier
|
658
|
+
hash_key_symbol(s.column)
|
659
|
+
when SQL::AliasedExpression
|
660
|
+
hash_key_symbol(s.aliaz)
|
661
|
+
else
|
662
|
+
raise(Error, "#{s.inspect} is not supported, should be a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
|
663
|
+
end
|
570
664
|
end
|
571
665
|
|
572
666
|
# Modify the identifier returned from the database based on the
|
@@ -583,6 +677,20 @@ module Sequel
|
|
583
677
|
def post_load(all_records)
|
584
678
|
end
|
585
679
|
|
680
|
+
# Called by insert/update/delete when returning is used.
|
681
|
+
# Yields each row as a plain hash to the block if one is given, or returns
|
682
|
+
# an array of plain hashes for all rows if a block is not given
|
683
|
+
def returning_fetch_rows(sql, &block)
|
684
|
+
if block
|
685
|
+
default_server.fetch_rows(sql, &block)
|
686
|
+
nil
|
687
|
+
else
|
688
|
+
rows = []
|
689
|
+
default_server.fetch_rows(sql){|r| rows << r}
|
690
|
+
rows
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
586
694
|
# Return the unaliased part of the identifier. Handles both
|
587
695
|
# implicit aliases in symbols, as well as SQL::AliasedExpression
|
588
696
|
# objects. Other objects are returned as is.
|
@@ -593,6 +701,14 @@ module Sequel
|
|
593
701
|
c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym
|
594
702
|
when SQL::AliasedExpression
|
595
703
|
c.expression
|
704
|
+
when SQL::OrderedExpression
|
705
|
+
expr = c.expression
|
706
|
+
if expr.is_a?(Symbol)
|
707
|
+
expr = unaliased_identifier(expr)
|
708
|
+
SQL::OrderedExpression.new(unaliased_identifier(c.expression), c.descending, :nulls=>c.nulls)
|
709
|
+
else
|
710
|
+
c
|
711
|
+
end
|
596
712
|
else
|
597
713
|
c
|
598
714
|
end
|