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