sequel 3.3.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +62 -0
- data/README.rdoc +4 -4
- data/doc/release_notes/3.3.0.txt +1 -1
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/sharding.rdoc +3 -3
- data/lib/sequel/adapters/amalgalite.rb +1 -1
- data/lib/sequel/adapters/firebird.rb +4 -9
- data/lib/sequel/adapters/jdbc.rb +21 -7
- data/lib/sequel/adapters/mysql.rb +2 -1
- data/lib/sequel/adapters/odbc.rb +7 -21
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +6 -1
- data/lib/sequel/adapters/shared/mssql.rb +11 -0
- data/lib/sequel/adapters/shared/mysql.rb +8 -12
- data/lib/sequel/adapters/shared/oracle.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +5 -10
- data/lib/sequel/adapters/shared/sqlite.rb +21 -1
- data/lib/sequel/adapters/sqlite.rb +2 -2
- data/lib/sequel/core.rb +147 -11
- data/lib/sequel/database.rb +21 -9
- data/lib/sequel/dataset.rb +31 -6
- data/lib/sequel/dataset/convenience.rb +1 -1
- data/lib/sequel/dataset/sql.rb +76 -18
- data/lib/sequel/extensions/inflector.rb +2 -51
- data/lib/sequel/model.rb +16 -10
- data/lib/sequel/model/associations.rb +4 -1
- data/lib/sequel/model/base.rb +13 -6
- data/lib/sequel/model/default_inflections.rb +46 -0
- data/lib/sequel/model/inflections.rb +1 -51
- data/lib/sequel/plugins/boolean_readers.rb +52 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +13 -1
- data/lib/sequel/plugins/nested_attributes.rb +171 -0
- data/lib/sequel/plugins/serialization.rb +35 -16
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/validation_helpers.rb +8 -1
- data/lib/sequel/sql.rb +33 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +11 -6
- data/spec/core/core_sql_spec.rb +29 -0
- data/spec/core/database_spec.rb +16 -7
- data/spec/core/dataset_spec.rb +264 -20
- data/spec/extensions/boolean_readers_spec.rb +86 -0
- data/spec/extensions/inflector_spec.rb +67 -4
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +45 -5
- data/spec/extensions/nested_attributes_spec.rb +272 -0
- data/spec/extensions/serialization_spec.rb +64 -1
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/validation_helpers_spec.rb +18 -0
- data/spec/integration/dataset_test.rb +79 -2
- data/spec/integration/schema_test.rb +17 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/model/associations_spec.rb +19 -7
- data/spec/model/model_spec.rb +29 -0
- data/spec/model/record_spec.rb +36 -0
- data/spec/spec_config.rb +1 -1
- metadata +14 -2
@@ -32,10 +32,10 @@ module Sequel
|
|
32
32
|
db.busy_timeout(opts.fetch(:timeout, 5000))
|
33
33
|
db.type_translation = true
|
34
34
|
|
35
|
-
# Handle
|
35
|
+
# Handle datetimes with Sequel.datetime_class
|
36
36
|
prok = proc do |t,v|
|
37
37
|
v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
|
38
|
-
Sequel.
|
38
|
+
Sequel.database_to_application_timestamp(v)
|
39
39
|
end
|
40
40
|
db.translator.add_translator("timestamp", &prok)
|
41
41
|
db.translator.add_translator("datetime", &prok)
|
data/lib/sequel/core.rb
CHANGED
@@ -29,15 +29,58 @@
|
|
29
29
|
#
|
30
30
|
# Sequel.datetime_class = DateTime
|
31
31
|
#
|
32
|
+
# Sequel doesn't pay much attention to timezones by default, but you can set it
|
33
|
+
# handle timezones if you want. There are three separate timezone settings:
|
34
|
+
#
|
35
|
+
# * application_timezone - The timezone you want the application to use. This is
|
36
|
+
# the timezone that incoming times from the database and typecasting are converted
|
37
|
+
# to.
|
38
|
+
# * database_timezone - The timezone for storage in the database. This is the
|
39
|
+
# timezone to which Sequel will convert timestamps before literalizing them
|
40
|
+
# for storage in the database. It is also the timezone that Sequel will assume
|
41
|
+
# database timestamp values are already in (if they don't include an offset).
|
42
|
+
# * typecast_timezone - The timezone that incoming data that Sequel needs to typecast
|
43
|
+
# is assumed to be already in (if they don't include an offset).
|
44
|
+
#
|
45
|
+
# You can set also set all three timezones to the same value at once via
|
46
|
+
# Sequel.default_timezone=.
|
47
|
+
#
|
48
|
+
# Sequel.application_timezone = :utc # or :local or nil
|
49
|
+
# Sequel.database_timezone = :utc # or :local or nil
|
50
|
+
# Sequel.typecast_timezone = :utc # or :local or nil
|
51
|
+
# Sequel.default_timezone = :utc # or :local or nil
|
52
|
+
#
|
53
|
+
# The only timezone values that are supported by default are :utc (convert to UTC),
|
54
|
+
# :local (convert to local time), and nil (don't convert). If you need to
|
55
|
+
# convert to a specific timezone, or need the timezones being used to change based
|
56
|
+
# on the environment (e.g. current user), you need to use an extension (and use
|
57
|
+
# DateTime as the datetime_class).
|
58
|
+
#
|
32
59
|
# You can set the SEQUEL_NO_CORE_EXTENSIONS constant or environment variable to have
|
33
60
|
# Sequel not extend the core classes.
|
34
61
|
module Sequel
|
62
|
+
# The offset of the current time zone from UTC, in seconds.
|
63
|
+
LOCAL_DATETIME_OFFSET_SECS = Time.now.utc_offset
|
64
|
+
|
65
|
+
# The offset of the current time zone from UTC, as a fraction of a day.
|
66
|
+
LOCAL_DATETIME_OFFSET = respond_to?(:Rational, true) ? Rational(LOCAL_DATETIME_OFFSET_SECS, 60*60*24) : LOCAL_DATETIME_OFFSET_SECS/60/60/24.0
|
67
|
+
|
68
|
+
@application_timezone = nil
|
35
69
|
@convert_two_digit_years = true
|
70
|
+
@database_timezone = nil
|
36
71
|
@datetime_class = Time
|
72
|
+
@typecast_timezone = nil
|
37
73
|
@virtual_row_instance_eval = true
|
38
74
|
|
39
75
|
class << self
|
40
76
|
attr_accessor :convert_two_digit_years, :datetime_class, :virtual_row_instance_eval
|
77
|
+
attr_accessor :application_timezone, :database_timezone, :typecast_timezone
|
78
|
+
end
|
79
|
+
|
80
|
+
# Convert the given Time/DateTime object into the database timezone, used when
|
81
|
+
# literalizing objects in an SQL string.
|
82
|
+
def self.application_to_database_timestamp(v)
|
83
|
+
convert_output_timestamp(v, Sequel.database_timezone)
|
41
84
|
end
|
42
85
|
|
43
86
|
# Returns true if the passed object could be a specifier of conditions, false otherwise.
|
@@ -73,6 +116,29 @@ module Sequel
|
|
73
116
|
Database.connect(*args, &block)
|
74
117
|
end
|
75
118
|
|
119
|
+
# Convert the given object into an object of Sequel.datetime_class in the
|
120
|
+
# application_timezone. Used when coverting datetime/timestamp columns
|
121
|
+
# returned by the database.
|
122
|
+
def self.database_to_application_timestamp(v)
|
123
|
+
convert_timestamp(v, Sequel.database_timezone)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Sets the database, application, and typecasting timezones to the given timezone.
|
127
|
+
def self.default_timezone=(tz)
|
128
|
+
self.database_timezone = tz
|
129
|
+
self.application_timezone = tz
|
130
|
+
self.typecast_timezone = tz
|
131
|
+
end
|
132
|
+
|
133
|
+
# Load all Sequel extensions given. Only loads extensions included in this
|
134
|
+
# release of Sequel, doesn't load external extensions.
|
135
|
+
#
|
136
|
+
# Sequel.extension(:schema_dumper)
|
137
|
+
# Sequel.extension(:pagination, :query)
|
138
|
+
def self.extension(*extensions)
|
139
|
+
require(extensions, 'extensions')
|
140
|
+
end
|
141
|
+
|
76
142
|
# Set the method to call on identifiers going into the database. This affects
|
77
143
|
# the literalization of identifiers by calling this method on them before they are input.
|
78
144
|
# Sequel upcases identifiers in all SQL strings for most databases, so to turn that off:
|
@@ -112,15 +178,6 @@ module Sequel
|
|
112
178
|
Database.quote_identifiers = value
|
113
179
|
end
|
114
180
|
|
115
|
-
# Load all Sequel extensions given. Only loads extensions included in this
|
116
|
-
# release of Sequel, doesn't load external extensions.
|
117
|
-
#
|
118
|
-
# Sequel.extension(:schema_dumper)
|
119
|
-
# Sequel.extension(:pagination, :query)
|
120
|
-
def self.extension(*extensions)
|
121
|
-
require(extensions, 'extensions')
|
122
|
-
end
|
123
|
-
|
124
181
|
# Require all given files which should be in the same or a subdirectory of
|
125
182
|
# this file. If a subdir is given, assume all files are in that subdir.
|
126
183
|
def self.require(files, subdir=nil)
|
@@ -168,7 +225,14 @@ module Sequel
|
|
168
225
|
raise InvalidValue, "Invalid Time value #{s.inspect} (#{e.message})"
|
169
226
|
end
|
170
227
|
end
|
171
|
-
|
228
|
+
|
229
|
+
# Convert the given object into an object of Sequel.datetime_class in the
|
230
|
+
# application_timezone. Used when typecasting values when assigning them
|
231
|
+
# to model datetime attributes.
|
232
|
+
def self.typecast_to_application_timestamp(v)
|
233
|
+
convert_timestamp(v, Sequel.typecast_timezone)
|
234
|
+
end
|
235
|
+
|
172
236
|
### Private Class Methods ###
|
173
237
|
|
174
238
|
# Helper method that the database adapter class methods that are added to Sequel via
|
@@ -184,6 +248,78 @@ module Sequel
|
|
184
248
|
end
|
185
249
|
connect(opts, &block)
|
186
250
|
end
|
251
|
+
|
252
|
+
# Converts the object from a String, Array, Date, DateTime, or Time into an
|
253
|
+
# instance of Sequel.datetime_class. If a string and an offset is not given,
|
254
|
+
# assume that the string is already in the given input_timezone.
|
255
|
+
def self.convert_input_timestamp(v, input_timezone) # :nodoc:
|
256
|
+
case v
|
257
|
+
when String
|
258
|
+
v2 = Sequel.string_to_datetime(v)
|
259
|
+
if !input_timezone || Date._parse(v).has_key?(:offset)
|
260
|
+
v2
|
261
|
+
else
|
262
|
+
# Correct for potentially wrong offset if offset is given
|
263
|
+
if v2.is_a?(DateTime)
|
264
|
+
# DateTime assumes UTC if no offset is given
|
265
|
+
v2 = v2.new_offset(LOCAL_DATETIME_OFFSET) - LOCAL_DATETIME_OFFSET if input_timezone == :local
|
266
|
+
else
|
267
|
+
# Time assumes local time if no offset is given
|
268
|
+
v2 = v2.getutc + LOCAL_DATETIME_OFFSET_SECS if input_timezone == :utc
|
269
|
+
end
|
270
|
+
v2
|
271
|
+
end
|
272
|
+
when Array
|
273
|
+
y, mo, d, h, mi, s = v
|
274
|
+
if datetime_class == DateTime
|
275
|
+
DateTime.civil(y, mo, d, h, mi, s, input_timezone == :utc ? 0 : LOCAL_DATETIME_OFFSET)
|
276
|
+
else
|
277
|
+
Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s)
|
278
|
+
end
|
279
|
+
when Time
|
280
|
+
if datetime_class == DateTime
|
281
|
+
v.respond_to?(:to_datetime) ? v.to_datetime : string_to_datetime(v.iso8601)
|
282
|
+
else
|
283
|
+
v
|
284
|
+
end
|
285
|
+
when DateTime
|
286
|
+
if datetime_class == DateTime
|
287
|
+
v
|
288
|
+
else
|
289
|
+
v.respond_to?(:to_time) ? v.to_time : string_to_datetime(v.to_s)
|
290
|
+
end
|
291
|
+
when Date
|
292
|
+
convert_input_timestamp(v.to_s, input_timezone)
|
293
|
+
else
|
294
|
+
raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Converts the object to the given output_timezone.
|
299
|
+
def self.convert_output_timestamp(v, output_timezone) # :nodoc:
|
300
|
+
if output_timezone
|
301
|
+
if v.is_a?(DateTime)
|
302
|
+
v.new_offset(output_timezone == :utc ? 0 : LOCAL_DATETIME_OFFSET)
|
303
|
+
else
|
304
|
+
v.send(output_timezone == :utc ? :getutc : :getlocal)
|
305
|
+
end
|
306
|
+
else
|
307
|
+
v
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Converts the given object from the given input timezone to the
|
312
|
+
# application timezone using convert_input_timestamp and
|
313
|
+
# convert_output_timestamp.
|
314
|
+
def self.convert_timestamp(v, input_timezone) # :nodoc:
|
315
|
+
begin
|
316
|
+
convert_output_timestamp(convert_input_timestamp(v, input_timezone), Sequel.application_timezone)
|
317
|
+
rescue InvalidValue
|
318
|
+
raise
|
319
|
+
rescue
|
320
|
+
raise InvalidValue, "Invalid #{datetime_class} value: #{v.inspect}"
|
321
|
+
end
|
322
|
+
end
|
187
323
|
|
188
324
|
# Method that adds a database adapter class method to Sequel that calls
|
189
325
|
# Sequel.adapter_method.
|
@@ -193,7 +329,7 @@ module Sequel
|
|
193
329
|
end
|
194
330
|
end
|
195
331
|
|
196
|
-
private_class_method :adapter_method, :def_adapter_method
|
332
|
+
private_class_method :adapter_method, :convert_input_timestamp, :convert_output_timestamp, :convert_timestamp, :def_adapter_method
|
197
333
|
|
198
334
|
require(%w"metaprogramming sql connection_pool exceptions dataset database version")
|
199
335
|
require(%w"schema_generator schema_methods schema_sql", 'database')
|
data/lib/sequel/database.rb
CHANGED
@@ -234,9 +234,10 @@ module Sequel
|
|
234
234
|
|
235
235
|
### Instance Methods ###
|
236
236
|
|
237
|
-
#
|
237
|
+
# Runs the supplied SQL statement string on the database server.
|
238
|
+
# Alias for run.
|
238
239
|
def <<(sql)
|
239
|
-
|
240
|
+
run(sql)
|
240
241
|
end
|
241
242
|
|
242
243
|
# Returns a dataset from the database. If the first argument is a string,
|
@@ -429,6 +430,14 @@ module Sequel
|
|
429
430
|
@quote_identifiers = @opts.include?(:quote_identifiers) ? @opts[:quote_identifiers] : (@@quote_identifiers.nil? ? quote_identifiers_default : @@quote_identifiers)
|
430
431
|
end
|
431
432
|
|
433
|
+
# Runs the supplied SQL statement string on the database server. Returns nil.
|
434
|
+
# Options:
|
435
|
+
# * :server - The server to run the SQL on.
|
436
|
+
def run(sql, opts={})
|
437
|
+
execute_ddl(sql, opts)
|
438
|
+
nil
|
439
|
+
end
|
440
|
+
|
432
441
|
# Returns a new dataset with the select method invoked.
|
433
442
|
def select(*args, &block)
|
434
443
|
dataset.select(*args, &block)
|
@@ -924,6 +933,8 @@ module Sequel
|
|
924
933
|
Date.new(value.year, value.month, value.day)
|
925
934
|
when String
|
926
935
|
Sequel.string_to_date(value)
|
936
|
+
when Hash
|
937
|
+
Date.new(*[:year, :month, :day].map{|x| (value[x] || value[x.to_s]).to_i})
|
927
938
|
else
|
928
939
|
raise InvalidValue, "invalid value for Date: #{value.inspect}"
|
929
940
|
end
|
@@ -931,14 +942,12 @@ module Sequel
|
|
931
942
|
|
932
943
|
# Typecast the value to a DateTime or Time depending on Sequel.datetime_class
|
933
944
|
def typecast_value_datetime(value)
|
934
|
-
raise(Sequel::InvalidValue, "invalid value for Datetime: #{value.inspect}") unless [DateTime, Date, Time, String].any?{|c| value.is_a?(c)}
|
935
|
-
|
936
|
-
|
937
|
-
|
945
|
+
raise(Sequel::InvalidValue, "invalid value for Datetime: #{value.inspect}") unless [DateTime, Date, Time, String, Hash].any?{|c| value.is_a?(c)}
|
946
|
+
klass = Sequel.datetime_class
|
947
|
+
if value.is_a?(Hash)
|
948
|
+
klass.send(klass == Time ? :mktime : :new, *[:year, :month, :day, :hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
938
949
|
else
|
939
|
-
|
940
|
-
# parse that string using the time class.
|
941
|
-
Sequel.string_to_datetime(Time === value ? value.iso8601 : value.to_s)
|
950
|
+
Sequel.typecast_to_application_timestamp(value)
|
942
951
|
end
|
943
952
|
end
|
944
953
|
|
@@ -976,6 +985,9 @@ module Sequel
|
|
976
985
|
value
|
977
986
|
when String
|
978
987
|
Sequel.string_to_time(value)
|
988
|
+
when Hash
|
989
|
+
t = Time.now
|
990
|
+
Time.mktime(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
979
991
|
else
|
980
992
|
raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
|
981
993
|
end
|
data/lib/sequel/dataset.rb
CHANGED
@@ -42,13 +42,13 @@ module Sequel
|
|
42
42
|
|
43
43
|
# All methods that should have a ! method added that modifies
|
44
44
|
# the receiver.
|
45
|
-
MUTATION_METHODS = %w'add_graph_aliases and distinct exclude
|
45
|
+
MUTATION_METHODS = %w'add_graph_aliases and distinct except exclude
|
46
46
|
filter from from_self full_outer_join graph
|
47
|
-
group group_and_count group_by having inner_join intersect invert join
|
48
|
-
left_outer_join limit naked or order order_by order_more paginate qualify query
|
49
|
-
reverse reverse_order right_outer_join select select_all select_more
|
50
|
-
set_defaults set_graph_aliases set_overrides
|
51
|
-
|
47
|
+
group group_and_count group_by having inner_join intersect invert join join_table
|
48
|
+
left_outer_join limit naked or order order_by order_more paginate qualify query
|
49
|
+
reverse reverse_order right_outer_join select select_all select_more server
|
50
|
+
set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
|
51
|
+
unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym}
|
52
52
|
|
53
53
|
NOTIMPL_MSG = "This method must be overridden in Sequel adapters".freeze
|
54
54
|
WITH_SUPPORTED='with'.freeze
|
@@ -175,6 +175,10 @@ module Sequel
|
|
175
175
|
|
176
176
|
# Iterates over the records in the dataset as they are yielded from the
|
177
177
|
# database adapter, and returns self.
|
178
|
+
#
|
179
|
+
# Note that this method is not safe to use on many adapters if you are
|
180
|
+
# running additional queries inside the provided block. If you are
|
181
|
+
# running queries inside the block, you use should all instead of each.
|
178
182
|
def each(&block)
|
179
183
|
if @opts[:graph]
|
180
184
|
graph_each(&block)
|
@@ -275,10 +279,25 @@ module Sequel
|
|
275
279
|
true
|
276
280
|
end
|
277
281
|
|
282
|
+
# Whether the dataset supports timezones in literal timestamps
|
283
|
+
def supports_timestamp_timezones?
|
284
|
+
false
|
285
|
+
end
|
286
|
+
|
287
|
+
# Whether the dataset supports fractional seconds in literal timestamps
|
288
|
+
def supports_timestamp_usecs?
|
289
|
+
true
|
290
|
+
end
|
291
|
+
|
278
292
|
# Whether the dataset supports window functions.
|
279
293
|
def supports_window_functions?
|
280
294
|
false
|
281
295
|
end
|
296
|
+
|
297
|
+
# Truncates the dataset. Returns nil.
|
298
|
+
def truncate
|
299
|
+
execute_ddl(truncate_sql)
|
300
|
+
end
|
282
301
|
|
283
302
|
# Updates values for the dataset. The returned value is generally the
|
284
303
|
# number of rows updated, but that is adapter dependent.
|
@@ -314,6 +333,12 @@ module Sequel
|
|
314
333
|
@db.execute(sql, {:server=>@opts[:server] || :read_only}.merge(opts), &block)
|
315
334
|
end
|
316
335
|
|
336
|
+
# Execute the given SQL on the database using execute_ddl.
|
337
|
+
def execute_ddl(sql, opts={}, &block)
|
338
|
+
@db.execute_ddl(sql, default_server_opts(opts), &block)
|
339
|
+
nil
|
340
|
+
end
|
341
|
+
|
317
342
|
# Execute the given SQL on the database using execute_dui.
|
318
343
|
def execute_dui(sql, opts={}, &block)
|
319
344
|
@db.execute_dui(sql, default_server_opts(opts), &block)
|
@@ -109,7 +109,7 @@ module Sequel
|
|
109
109
|
# # this will commit every 50 records
|
110
110
|
# dataset.import([:x, :y], [[1, 2], [3, 4], ...], :slice => 50)
|
111
111
|
def import(columns, values, opts={})
|
112
|
-
return @db.transaction{execute_dui("#{insert_sql_base}#{quote_schema_table(@opts[:from].first)} (#{identifier_list(columns)})
|
112
|
+
return @db.transaction{execute_dui("#{insert_sql_base}#{quote_schema_table(@opts[:from].first)} (#{identifier_list(columns)}) #{subselect_sql(values)}")} if values.is_a?(Dataset)
|
113
113
|
|
114
114
|
return if values.empty?
|
115
115
|
raise(Error, IMPORT_ERROR_MSG) if columns.empty?
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -17,6 +17,8 @@ module Sequel
|
|
17
17
|
QUESTION_MARK = '?'.freeze
|
18
18
|
STOCK_COUNT_OPTS = {:select => [SQL::AliasedExpression.new(LiteralString.new("COUNT(*)").freeze, :count)], :order => nil}.freeze
|
19
19
|
SELECT_CLAUSE_ORDER = %w'with distinct columns from join where group having compounds order limit'.freeze
|
20
|
+
TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze
|
21
|
+
STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP #{TIMESTAMP_FORMAT}".freeze
|
20
22
|
TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
|
21
23
|
WILDCARD = '*'.freeze
|
22
24
|
SQL_WITH = "WITH ".freeze
|
@@ -88,6 +90,11 @@ module Sequel
|
|
88
90
|
raise(InvalidOperation, "invalid operator #{op}")
|
89
91
|
end
|
90
92
|
end
|
93
|
+
|
94
|
+
# SQL fragment for constants
|
95
|
+
def constant_sql(constant)
|
96
|
+
constant.to_s
|
97
|
+
end
|
91
98
|
|
92
99
|
# Returns the number of records in the dataset.
|
93
100
|
def count
|
@@ -103,11 +110,7 @@ module Sequel
|
|
103
110
|
|
104
111
|
return static_sql(opts[:sql]) if opts[:sql]
|
105
112
|
|
106
|
-
|
107
|
-
raise InvalidOperation, "Grouped datasets cannot be deleted from"
|
108
|
-
elsif opts[:from].is_a?(Array) && opts[:from].size > 1
|
109
|
-
raise InvalidOperation, "Joined datasets cannot be deleted from"
|
110
|
-
end
|
113
|
+
check_modification_allowed!
|
111
114
|
|
112
115
|
sql = "DELETE FROM #{source_list(opts[:from])}"
|
113
116
|
|
@@ -308,7 +311,7 @@ module Sequel
|
|
308
311
|
# dataset.group(:id) # SELECT * FROM items GROUP BY id
|
309
312
|
# dataset.group(:id, :name) # SELECT * FROM items GROUP BY id, name
|
310
313
|
def group(*columns)
|
311
|
-
clone(:group => columns)
|
314
|
+
clone(:group => (columns.compact.empty? ? nil : columns))
|
312
315
|
end
|
313
316
|
alias group_by group
|
314
317
|
|
@@ -344,6 +347,8 @@ module Sequel
|
|
344
347
|
def insert_sql(*values)
|
345
348
|
return static_sql(@opts[:sql]) if @opts[:sql]
|
346
349
|
|
350
|
+
check_modification_allowed!
|
351
|
+
|
347
352
|
from = source_list(@opts[:from])
|
348
353
|
case values.size
|
349
354
|
when 0
|
@@ -778,14 +783,32 @@ module Sequel
|
|
778
783
|
def subscript_sql(s)
|
779
784
|
"#{literal(s.f)}[#{expression_list(s.sub)}]"
|
780
785
|
end
|
786
|
+
|
787
|
+
# SQL query to truncate the table
|
788
|
+
def truncate_sql
|
789
|
+
if opts[:sql]
|
790
|
+
static_sql(opts[:sql])
|
791
|
+
else
|
792
|
+
check_modification_allowed!
|
793
|
+
raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where]
|
794
|
+
_truncate_sql(source_list(opts[:from]))
|
795
|
+
end
|
796
|
+
end
|
781
797
|
|
782
798
|
# Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
|
783
799
|
#
|
784
|
-
# dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items
|
800
|
+
# dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items GROUP BY a
|
785
801
|
def unfiltered
|
786
802
|
clone(:where => nil, :having => nil)
|
787
803
|
end
|
788
804
|
|
805
|
+
# Returns a copy of the dataset with no grouping (GROUP or HAVING clause) applied.
|
806
|
+
#
|
807
|
+
# dataset.group(:a).having(:a=>1).where(:b).ungrouped # SELECT * FROM items WHERE b
|
808
|
+
def ungrouped
|
809
|
+
clone(:group => nil, :having => nil)
|
810
|
+
end
|
811
|
+
|
789
812
|
# Adds a UNION clause using a second dataset object.
|
790
813
|
# A UNION compound dataset returns all rows in either the current dataset
|
791
814
|
# or the given dataset.
|
@@ -826,11 +849,7 @@ module Sequel
|
|
826
849
|
|
827
850
|
return static_sql(opts[:sql]) if opts[:sql]
|
828
851
|
|
829
|
-
|
830
|
-
raise InvalidOperation, "A grouped dataset cannot be updated"
|
831
|
-
elsif (opts[:from].size > 1) or opts[:join]
|
832
|
-
raise InvalidOperation, "A joined dataset cannot be updated"
|
833
|
-
end
|
852
|
+
check_modification_allowed!
|
834
853
|
|
835
854
|
sql = "UPDATE #{source_list(@opts[:from])} SET "
|
836
855
|
set = if values.is_a?(Hash)
|
@@ -945,6 +964,13 @@ module Sequel
|
|
945
964
|
def as_sql(expression, aliaz)
|
946
965
|
"#{expression} AS #{quote_identifier(aliaz)}"
|
947
966
|
end
|
967
|
+
|
968
|
+
# Raise an InvalidOperation exception if deletion is not allowed
|
969
|
+
# for this dataset
|
970
|
+
def check_modification_allowed!
|
971
|
+
raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
|
972
|
+
raise(InvalidOperation, "Joined datasets cannot be modified") if (opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join]
|
973
|
+
end
|
948
974
|
|
949
975
|
# Converts an array of column names into a comma seperated string of
|
950
976
|
# column names. If the array is empty, a wildcard (*) is returned.
|
@@ -969,6 +995,11 @@ module Sequel
|
|
969
995
|
columns.map{|i| literal(i)}.join(COMMA_SEPARATOR)
|
970
996
|
end
|
971
997
|
|
998
|
+
# The strftime format to use when literalizing the time.
|
999
|
+
def default_timestamp_format
|
1000
|
+
requires_sql_standard_datetimes? ? STANDARD_TIMESTAMP_FORMAT : TIMESTAMP_FORMAT
|
1001
|
+
end
|
1002
|
+
|
972
1003
|
# SQL fragment based on the expr type. See #filter.
|
973
1004
|
def filter_expr(expr = nil, &block)
|
974
1005
|
expr = nil if expr == []
|
@@ -1002,6 +1033,27 @@ module Sequel
|
|
1002
1033
|
raise(Error, 'Invalid filter argument')
|
1003
1034
|
end
|
1004
1035
|
end
|
1036
|
+
|
1037
|
+
# Format the timestamp based on the default_timestamp_format, with a couple
|
1038
|
+
# of modifiers. First, allow %N to be used for fractions seconds (if the
|
1039
|
+
# database supports them), and override %z to always use a numeric offset
|
1040
|
+
# of hours and minutes.
|
1041
|
+
def format_timestamp(v)
|
1042
|
+
v2 = Sequel.application_to_database_timestamp(v)
|
1043
|
+
fmt = default_timestamp_format.gsub(/%[Nz]/) do |m|
|
1044
|
+
if m == '%N'
|
1045
|
+
sprintf(".%06d", v.is_a?(DateTime) ? v.sec_fraction*86400000000 : v.usec) if supports_timestamp_usecs?
|
1046
|
+
else
|
1047
|
+
if supports_timestamp_timezones?
|
1048
|
+
# Would like to just use %z format, but it doesn't appear to work on Windows
|
1049
|
+
# Instead, the offset fragment is constructed manually
|
1050
|
+
minutes = (v2.is_a?(DateTime) ? v2.offset * 1440 : v2.utc_offset/60).to_i
|
1051
|
+
sprintf("%+03i%02i", *minutes.divmod(60))
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
v2.strftime(fmt)
|
1056
|
+
end
|
1005
1057
|
|
1006
1058
|
# SQL fragment specifying a list of identifiers
|
1007
1059
|
def identifier_list(columns)
|
@@ -1035,7 +1087,7 @@ module Sequel
|
|
1035
1087
|
order.map do |f|
|
1036
1088
|
case f
|
1037
1089
|
when SQL::OrderedExpression
|
1038
|
-
|
1090
|
+
f.invert
|
1039
1091
|
else
|
1040
1092
|
SQL::OrderedExpression.new(f)
|
1041
1093
|
end
|
@@ -1074,9 +1126,9 @@ module Sequel
|
|
1074
1126
|
requires_sql_standard_datetimes? ? v.strftime("DATE '%Y-%m-%d'") : "'#{v}'"
|
1075
1127
|
end
|
1076
1128
|
|
1077
|
-
# SQL fragment for DateTime
|
1129
|
+
# SQL fragment for DateTime
|
1078
1130
|
def literal_datetime(v)
|
1079
|
-
|
1131
|
+
format_timestamp(v)
|
1080
1132
|
end
|
1081
1133
|
|
1082
1134
|
# SQL fragment for SQL::Expression, result depends on the specific type of expression.
|
@@ -1128,12 +1180,12 @@ module Sequel
|
|
1128
1180
|
c_alias ? as_sql(qc, c_alias) : qc
|
1129
1181
|
end
|
1130
1182
|
|
1131
|
-
# SQL fragment for Time
|
1183
|
+
# SQL fragment for Time
|
1132
1184
|
def literal_time(v)
|
1133
|
-
|
1185
|
+
format_timestamp(v)
|
1134
1186
|
end
|
1135
1187
|
|
1136
|
-
# SQL fragment for true
|
1188
|
+
# SQL fragment for true
|
1137
1189
|
def literal_true
|
1138
1190
|
BOOL_TRUE
|
1139
1191
|
end
|
@@ -1323,5 +1375,11 @@ module Sequel
|
|
1323
1375
|
def table_ref(t)
|
1324
1376
|
t.is_a?(String) ? quote_identifier(t) : literal(t)
|
1325
1377
|
end
|
1378
|
+
|
1379
|
+
# Formats the truncate statement. Assumes the table given has already been
|
1380
|
+
# literalized.
|
1381
|
+
def _truncate_sql(table)
|
1382
|
+
"TRUNCATE TABLE #{table}"
|
1383
|
+
end
|
1326
1384
|
end
|
1327
1385
|
end
|