sequel 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +84 -0
- data/Rakefile +1 -1
- data/doc/cheat_sheet.rdoc +5 -2
- data/doc/opening_databases.rdoc +2 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/lib/sequel/adapters/ado.rb +3 -1
- data/lib/sequel/adapters/ado/mssql.rb +2 -2
- data/lib/sequel/adapters/do.rb +2 -11
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/do/postgres.rb +2 -2
- data/lib/sequel/adapters/firebird.rb +3 -3
- data/lib/sequel/adapters/informix.rb +3 -3
- data/lib/sequel/adapters/jdbc/h2.rb +3 -3
- data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
- data/lib/sequel/adapters/mysql.rb +60 -21
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +3 -3
- data/lib/sequel/adapters/oracle.rb +1 -5
- data/lib/sequel/adapters/postgres.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +142 -33
- data/lib/sequel/adapters/shared/mysql.rb +54 -31
- data/lib/sequel/adapters/shared/oracle.rb +17 -6
- data/lib/sequel/adapters/shared/postgres.rb +7 -7
- data/lib/sequel/adapters/shared/progress.rb +3 -3
- data/lib/sequel/adapters/shared/sqlite.rb +3 -17
- data/lib/sequel/connection_pool.rb +4 -6
- data/lib/sequel/core.rb +29 -113
- data/lib/sequel/database.rb +14 -12
- data/lib/sequel/dataset.rb +8 -21
- data/lib/sequel/dataset/convenience.rb +1 -1
- data/lib/sequel/dataset/graph.rb +9 -2
- data/lib/sequel/dataset/sql.rb +170 -104
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/schema_dumper.rb +7 -1
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -4
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/model/associations.rb +105 -45
- data/lib/sequel/model/base.rb +37 -28
- data/lib/sequel/plugins/active_model.rb +35 -0
- data/lib/sequel/plugins/association_dependencies.rb +96 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/force_encoding.rb +61 -0
- data/lib/sequel/plugins/many_through_many.rb +32 -11
- data/lib/sequel/plugins/nested_attributes.rb +7 -2
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +61 -0
- data/lib/sequel/sql.rb +31 -30
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +262 -0
- data/spec/adapters/mysql_spec.rb +46 -8
- data/spec/adapters/postgres_spec.rb +6 -3
- data/spec/adapters/spec_helper.rb +21 -0
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +1 -1
- data/spec/core/database_spec.rb +27 -1
- data/spec/core/dataset_spec.rb +63 -1
- data/spec/core/object_graph_spec.rb +1 -1
- data/spec/core/schema_spec.rb +1 -0
- data/spec/extensions/active_model_spec.rb +47 -0
- data/spec/extensions/association_dependencies_spec.rb +108 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/force_encoding_spec.rb +75 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +60 -2
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +29 -1
- data/spec/extensions/schema_dumper_spec.rb +10 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +60 -0
- data/spec/integration/database_test.rb +8 -0
- data/spec/integration/dataset_test.rb +9 -9
- data/spec/integration/plugin_test.rb +139 -0
- data/spec/integration/schema_test.rb +7 -7
- data/spec/integration/spec_helper.rb +32 -1
- data/spec/integration/timezone_test.rb +3 -3
- data/spec/integration/transaction_test.rb +1 -1
- data/spec/integration/type_test.rb +6 -6
- data/spec/model/association_reflection_spec.rb +18 -0
- data/spec/model/associations_spec.rb +169 -9
- data/spec/model/base_spec.rb +2 -0
- data/spec/model/eager_loading_spec.rb +82 -2
- data/spec/model/model_spec.rb +8 -1
- data/spec/model/record_spec.rb +52 -9
- metadata +33 -23
data/lib/sequel/database.rb
CHANGED
@@ -31,6 +31,7 @@ module Sequel
|
|
31
31
|
TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
|
32
32
|
|
33
33
|
POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
|
34
|
+
MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
|
34
35
|
MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
|
35
36
|
STRING_DEFAULT_RE = /\A'(.*)'\z/
|
36
37
|
|
@@ -109,7 +110,7 @@ module Sequel
|
|
109
110
|
begin
|
110
111
|
Sequel.require "adapters/#{scheme}"
|
111
112
|
rescue LoadError => e
|
112
|
-
raise
|
113
|
+
raise Sequel.convert_exception_class(e, AdapterNotFound)
|
113
114
|
end
|
114
115
|
|
115
116
|
# make sure we actually loaded the adapter
|
@@ -533,10 +534,8 @@ module Sequel
|
|
533
534
|
meth = "typecast_value_#{column_type}"
|
534
535
|
begin
|
535
536
|
respond_to?(meth, true) ? send(meth, value) : value
|
536
|
-
rescue ArgumentError, TypeError =>
|
537
|
-
|
538
|
-
e.set_backtrace(exp.backtrace)
|
539
|
-
raise e
|
537
|
+
rescue ArgumentError, TypeError => e
|
538
|
+
raise Sequel.convert_exception_class(e, InvalidValue)
|
540
539
|
end
|
541
540
|
end
|
542
541
|
|
@@ -658,10 +657,13 @@ module Sequel
|
|
658
657
|
if database_type == :postgres and m = POSTGRES_DEFAULT_RE.match(default)
|
659
658
|
default = m[1] || m[2]
|
660
659
|
end
|
661
|
-
if
|
660
|
+
if database_type == :mssql and m = MSSQL_DEFAULT_RE.match(default)
|
661
|
+
default = m[1] || m[2]
|
662
|
+
end
|
663
|
+
if [:string, :blob, :date, :datetime, :time, :enum].include?(type)
|
662
664
|
if database_type == :mysql
|
663
665
|
return if [:date, :datetime, :time].include?(type) && MYSQL_TIMESTAMP_RE.match(default)
|
664
|
-
orig_default = default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
|
666
|
+
orig_default = default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
|
665
667
|
end
|
666
668
|
return unless m = STRING_DEFAULT_RE.match(default)
|
667
669
|
default = m[1].gsub("''", "'")
|
@@ -675,7 +677,7 @@ module Sequel
|
|
675
677
|
when /[t1]/i
|
676
678
|
true
|
677
679
|
end
|
678
|
-
when :string
|
680
|
+
when :string, :enum
|
679
681
|
default
|
680
682
|
when :blob
|
681
683
|
Sequel::SQL::Blob.new(default)
|
@@ -801,9 +803,7 @@ module Sequel
|
|
801
803
|
# and traceback.
|
802
804
|
def raise_error(exception, opts={})
|
803
805
|
if !opts[:classes] || Array(opts[:classes]).any?{|c| exception.is_a?(c)}
|
804
|
-
|
805
|
-
e.set_backtrace(exception.backtrace)
|
806
|
-
raise e
|
806
|
+
raise Sequel.convert_exception_class(exception, opts[:disconnect] ? DatabaseDisconnectError : DatabaseError)
|
807
807
|
else
|
808
808
|
raise exception
|
809
809
|
end
|
@@ -876,6 +876,8 @@ module Sequel
|
|
876
876
|
:decimal
|
877
877
|
when /bytea|blob|image|(var)?binary/io
|
878
878
|
:blob
|
879
|
+
when /\Aenum/
|
880
|
+
:enum
|
879
881
|
end
|
880
882
|
end
|
881
883
|
|
@@ -906,7 +908,7 @@ module Sequel
|
|
906
908
|
|
907
909
|
# Raise a database error unless the exception is an Rollback.
|
908
910
|
def transaction_error(e)
|
909
|
-
raise_error(e, :classes=>database_error_classes) unless Rollback
|
911
|
+
raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
|
910
912
|
end
|
911
913
|
|
912
914
|
# Typecast the value to an SQL::Blob
|
data/lib/sequel/dataset.rb
CHANGED
@@ -51,7 +51,7 @@ module Sequel
|
|
51
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
|
-
WITH_SUPPORTED
|
54
|
+
WITH_SUPPORTED=:select_with_sql
|
55
55
|
|
56
56
|
# The database that corresponds to this dataset
|
57
57
|
attr_accessor :db
|
@@ -97,7 +97,7 @@ module Sequel
|
|
97
97
|
# options of the resulting dataset.
|
98
98
|
def self.def_mutation_method(*meths)
|
99
99
|
meths.each do |meth|
|
100
|
-
class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
|
100
|
+
class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
@@ -163,12 +163,12 @@ module Sequel
|
|
163
163
|
# Add a mutation method to this dataset instance.
|
164
164
|
def def_mutation_method(*meths)
|
165
165
|
meths.each do |meth|
|
166
|
-
instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
|
166
|
+
instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
|
167
167
|
end
|
168
168
|
end
|
169
169
|
|
170
170
|
# Deletes the records in the dataset. The returned value is generally the
|
171
|
-
# number of records deleted, but that is adapter dependent.
|
171
|
+
# number of records deleted, but that is adapter dependent. See delete_sql.
|
172
172
|
def delete
|
173
173
|
execute_dui(delete_sql)
|
174
174
|
end
|
@@ -200,6 +200,7 @@ module Sequel
|
|
200
200
|
|
201
201
|
# Inserts values into the associated table. The returned value is generally
|
202
202
|
# the value of the primary key for the inserted row, but that is adapter dependent.
|
203
|
+
# See insert_sql.
|
203
204
|
def insert(*values)
|
204
205
|
execute_insert(insert_sql(*values))
|
205
206
|
end
|
@@ -242,7 +243,7 @@ module Sequel
|
|
242
243
|
update(*args)
|
243
244
|
end
|
244
245
|
|
245
|
-
# Set the default values for insert and update statements. The values passed
|
246
|
+
# Set the default values for insert and update statements. The values hash passed
|
246
247
|
# to insert or update are merged into this hash.
|
247
248
|
def set_defaults(hash)
|
248
249
|
clone(:defaults=>(@opts[:defaults]||{}).merge(hash))
|
@@ -256,7 +257,7 @@ module Sequel
|
|
256
257
|
|
257
258
|
# Whether the dataset supports common table expressions (the WITH clause).
|
258
259
|
def supports_cte?
|
259
|
-
|
260
|
+
select_clause_methods.include?(WITH_SUPPORTED)
|
260
261
|
end
|
261
262
|
|
262
263
|
# Whether the dataset supports the DISTINCT ON clause, true by default.
|
@@ -300,7 +301,7 @@ module Sequel
|
|
300
301
|
end
|
301
302
|
|
302
303
|
# Updates values for the dataset. The returned value is generally the
|
303
|
-
# number of rows updated, but that is adapter dependent.
|
304
|
+
# number of rows updated, but that is adapter dependent. See update_sql.
|
304
305
|
def update(values={})
|
305
306
|
execute_dui(update_sql(values))
|
306
307
|
end
|
@@ -377,19 +378,5 @@ module Sequel
|
|
377
378
|
# default, added to make the model eager loading code simpler.
|
378
379
|
def post_load(all_records)
|
379
380
|
end
|
380
|
-
|
381
|
-
# If a block argument is passed to a method that uses a VirtualRow,
|
382
|
-
# yield a new VirtualRow instance to the block if it accepts a single
|
383
|
-
# argument. Otherwise, evaluate the block in the context of a new
|
384
|
-
# VirtualRow instance.
|
385
|
-
def virtual_row_block_call(block)
|
386
|
-
return unless block
|
387
|
-
case block.arity
|
388
|
-
when -1, 0
|
389
|
-
SQL::VirtualRow.new.instance_eval(&block)
|
390
|
-
else
|
391
|
-
block.call(SQL::VirtualRow.new)
|
392
|
-
end
|
393
|
-
end
|
394
381
|
end
|
395
382
|
end
|
@@ -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{
|
112
|
+
return @db.transaction{insert(columns, 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/graph.rb
CHANGED
@@ -41,6 +41,10 @@ module Sequel
|
|
41
41
|
# or an object that responds to .dataset and return a symbol or a dataset
|
42
42
|
# * join_conditions - Any condition(s) allowed by join_table.
|
43
43
|
# * options - A hash of graph options. The following options are currently used:
|
44
|
+
# * :from_self_alias - The alias to use when the receiver is not a graphed
|
45
|
+
# dataset but it contains multiple FROM tables or a JOIN. In this case,
|
46
|
+
# the receiver is wrapped in a from_self before graphing, and this option
|
47
|
+
# determines the alias to use.
|
44
48
|
# * :implicit_qualifier - The qualifier of implicit conditions, see #join_table.
|
45
49
|
# * :join_type - The type of join to use (passed to join_table). Defaults to
|
46
50
|
# :left_outer.
|
@@ -83,9 +87,12 @@ module Sequel
|
|
83
87
|
|
84
88
|
# Only allow table aliases that haven't been used
|
85
89
|
raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
|
86
|
-
|
90
|
+
|
91
|
+
# Use a from_self if this is already a joined table
|
92
|
+
ds = (!@opts[:graph] && (@opts[:from].length > 1 || @opts[:join])) ? from_self(:alias=>options[:from_self_alias] || first_source) : self
|
93
|
+
|
87
94
|
# Join the table early in order to avoid cloning the dataset twice
|
88
|
-
ds = join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
|
95
|
+
ds = ds.join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
|
89
96
|
opts = ds.opts
|
90
97
|
|
91
98
|
# Whether to include the table in the result set
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
|
+
|
4
|
+
# Given a type (e.g. select) and an array of clauses,
|
5
|
+
# return an array of methods to call to build the SQL string.
|
6
|
+
def self.clause_methods(type, clauses)
|
7
|
+
clauses.map{|clause| :"#{type}_#{clause}_sql"}.freeze
|
8
|
+
end
|
9
|
+
|
3
10
|
AND_SEPARATOR = " AND ".freeze
|
4
11
|
BOOL_FALSE = "'f'".freeze
|
5
12
|
BOOL_TRUE = "'t'".freeze
|
@@ -8,7 +15,7 @@ module Sequel
|
|
8
15
|
COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
|
9
16
|
COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
|
10
17
|
DATASET_ALIAS_BASE_NAME = 't'.freeze
|
11
|
-
|
18
|
+
FROM_SELF_KEEP_OPTS = [:graph, :eager_graph, :graph_aliases]
|
12
19
|
IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
|
13
20
|
IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
|
14
21
|
N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
|
@@ -16,7 +23,10 @@ module Sequel
|
|
16
23
|
QUALIFY_KEYS = [:select, :where, :having, :order, :group]
|
17
24
|
QUESTION_MARK = '?'.freeze
|
18
25
|
STOCK_COUNT_OPTS = {:select => [SQL::AliasedExpression.new(LiteralString.new("COUNT(*)").freeze, :count)], :order => nil}.freeze
|
19
|
-
|
26
|
+
DELETE_CLAUSE_METHODS = clause_methods(:delete, %w'from where')
|
27
|
+
INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'into columns values')
|
28
|
+
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct columns from join where group having compounds order limit')
|
29
|
+
UPDATE_CLAUSE_METHODS = clause_methods(:update, %w'table set where')
|
20
30
|
TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze
|
21
31
|
STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP #{TIMESTAMP_FORMAT}".freeze
|
22
32
|
TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
|
@@ -106,19 +116,9 @@ module Sequel
|
|
106
116
|
# dataset.filter{|o| o.price >= 100}.delete_sql #=>
|
107
117
|
# "DELETE FROM items WHERE (price >= 100)"
|
108
118
|
def delete_sql
|
109
|
-
opts = @opts
|
110
|
-
|
111
119
|
return static_sql(opts[:sql]) if opts[:sql]
|
112
|
-
|
113
120
|
check_modification_allowed!
|
114
|
-
|
115
|
-
sql = "DELETE FROM #{source_list(opts[:from])}"
|
116
|
-
|
117
|
-
if where = opts[:where]
|
118
|
-
sql << " WHERE #{literal(where)}"
|
119
|
-
end
|
120
|
-
|
121
|
-
sql
|
121
|
+
clause_sql(:delete)
|
122
122
|
end
|
123
123
|
|
124
124
|
# Returns a copy of the dataset with the SQL DISTINCT clause.
|
@@ -168,7 +168,7 @@ module Sequel
|
|
168
168
|
# Returns an EXISTS clause for the dataset as a LiteralString.
|
169
169
|
#
|
170
170
|
# DB.select(1).where(DB[:items].exists).sql
|
171
|
-
# #=> "SELECT 1 WHERE EXISTS (SELECT * FROM items)"
|
171
|
+
# #=> "SELECT 1 WHERE (EXISTS (SELECT * FROM items))"
|
172
172
|
def exists
|
173
173
|
LiteralString.new("EXISTS (#{select_sql})")
|
174
174
|
end
|
@@ -283,7 +283,7 @@ module Sequel
|
|
283
283
|
# ds.from_self(:alias=>:foo).sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS 'foo'"
|
284
284
|
def from_self(opts={})
|
285
285
|
fs = {}
|
286
|
-
@opts.keys.each{|k| fs[k] = nil}
|
286
|
+
@opts.keys.each{|k| fs[k] = nil unless FROM_SELF_KEEP_OPTS.include?(k)}
|
287
287
|
clone(fs).from(opts[:alias] ? as(opts[:alias]) : self)
|
288
288
|
end
|
289
289
|
|
@@ -336,57 +336,60 @@ module Sequel
|
|
336
336
|
end
|
337
337
|
end
|
338
338
|
|
339
|
-
# Formats an INSERT statement using the given values.
|
340
|
-
#
|
341
|
-
# the resulting statement includes a DEFAULT VALUES clause.
|
339
|
+
# Formats an INSERT statement using the given values. The API is a little
|
340
|
+
# complex, and best explained by example:
|
342
341
|
#
|
343
|
-
#
|
344
|
-
#
|
345
|
-
#
|
346
|
-
#
|
342
|
+
# # Default values
|
343
|
+
# DB[:items].insert_sql #=> 'INSERT INTO items DEFAULT VALUES'
|
344
|
+
# DB[:items].insert_sql({}) #=> 'INSERT INTO items DEFAULT VALUES'
|
345
|
+
# # Values without columns
|
346
|
+
# DB[:items].insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
347
|
+
# DB[:items].insert_sql([1,2,3]) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
348
|
+
# # Values with columns
|
349
|
+
# DB[:items].insert_sql([:a, :b], [1,2]) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
350
|
+
# DB[:items].insert_sql(:a => 1, :b => 2) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
351
|
+
# # Using a subselect
|
352
|
+
# DB[:items].insert_sql(DB[:old_items]) #=> 'INSERT INTO items SELECT * FROM old_items
|
353
|
+
# # Using a subselect with columns
|
354
|
+
# DB[:items].insert_sql([:a, :b], DB[:old_items]) #=> 'INSERT INTO items (a, b) SELECT * FROM old_items
|
347
355
|
def insert_sql(*values)
|
348
356
|
return static_sql(@opts[:sql]) if @opts[:sql]
|
349
357
|
|
350
358
|
check_modification_allowed!
|
351
359
|
|
352
|
-
|
360
|
+
columns = []
|
361
|
+
|
353
362
|
case values.size
|
354
363
|
when 0
|
355
|
-
|
364
|
+
return insert_sql({})
|
356
365
|
when 1
|
357
|
-
vals = values.at(0)
|
358
|
-
|
366
|
+
case vals = values.at(0)
|
367
|
+
when Hash
|
368
|
+
vals = @opts[:defaults].merge(vals) if @opts[:defaults]
|
369
|
+
vals = vals.merge(@opts[:overrides]) if @opts[:overrides]
|
370
|
+
values = []
|
371
|
+
vals.each do |k,v|
|
372
|
+
columns << k
|
373
|
+
values << v
|
374
|
+
end
|
375
|
+
when Dataset, Array, LiteralString
|
359
376
|
values = vals
|
360
|
-
elsif vals.respond_to?(:values)
|
361
|
-
values = vals.values
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
case values
|
366
|
-
when Array
|
367
|
-
if values.empty?
|
368
|
-
insert_default_values_sql
|
369
377
|
else
|
370
|
-
|
371
|
-
|
372
|
-
when Hash
|
373
|
-
values = @opts[:defaults].merge(values) if @opts[:defaults]
|
374
|
-
values = values.merge(@opts[:overrides]) if @opts[:overrides]
|
375
|
-
if values.empty?
|
376
|
-
insert_default_values_sql
|
377
|
-
else
|
378
|
-
fl, vl = [], []
|
379
|
-
values.each do |k, v|
|
380
|
-
fl << literal(String === k ? k.to_sym : k)
|
381
|
-
vl << literal(v)
|
378
|
+
if vals.respond_to?(:values) && (v = vals.values).is_a?(Hash)
|
379
|
+
return insert_sql(v)
|
382
380
|
end
|
383
|
-
"#{insert_sql_base}#{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})#{insert_sql_suffix}"
|
384
381
|
end
|
385
|
-
when
|
386
|
-
|
382
|
+
when 2
|
383
|
+
if (v0 = values.at(0)).is_a?(Array) && ((v1 = values.at(1)).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
|
384
|
+
columns, values = v0, v1
|
385
|
+
raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
|
386
|
+
end
|
387
387
|
end
|
388
|
+
|
389
|
+
columns = columns.map{|k| literal(String === k ? k.to_sym : k)}
|
390
|
+
clone(:columns=>columns, :values=>values)._insert_sql
|
388
391
|
end
|
389
|
-
|
392
|
+
|
390
393
|
# Adds an INTERSECT clause using a second dataset object.
|
391
394
|
# An INTERSECT compound dataset returns all rows in both the current dataset
|
392
395
|
# and the given dataset.
|
@@ -590,8 +593,7 @@ module Sequel
|
|
590
593
|
# This method should be overridden by descendants if the support
|
591
594
|
# inserting multiple records in a single SQL statement.
|
592
595
|
def multi_insert_sql(columns, values)
|
593
|
-
|
594
|
-
values.map{|r| s + literal(r)}
|
596
|
+
values.map{|r| insert_sql(columns, r)}
|
595
597
|
end
|
596
598
|
|
597
599
|
# Adds an alternate filter to an existing filter using OR. If no filter
|
@@ -619,7 +621,7 @@ module Sequel
|
|
619
621
|
# ds.order{|o| o.sum(:name)}.sql #=> 'SELECT * FROM items ORDER BY sum(name)'
|
620
622
|
# ds.order(nil).sql #=> 'SELECT * FROM items'
|
621
623
|
def order(*columns, &block)
|
622
|
-
columns += Array(
|
624
|
+
columns += Array(Sequel.virtual_row(&block)) if block
|
623
625
|
clone(:order => (columns.compact.empty?) ? nil : columns)
|
624
626
|
end
|
625
627
|
alias_method :order_by, :order
|
@@ -630,7 +632,8 @@ module Sequel
|
|
630
632
|
# ds.order(:a).order(:b).sql #=> 'SELECT * FROM items ORDER BY b'
|
631
633
|
# ds.order(:a).order_more(:b).sql #=> 'SELECT * FROM items ORDER BY a, b'
|
632
634
|
def order_more(*columns, &block)
|
633
|
-
|
635
|
+
columns = @opts[:order] + columns if @opts[:order]
|
636
|
+
order(*columns, &block)
|
634
637
|
end
|
635
638
|
|
636
639
|
# SQL fragment for the ordered expression, used in the ORDER BY
|
@@ -740,7 +743,7 @@ module Sequel
|
|
740
743
|
# dataset.select(:a, :b) # SELECT a, b FROM items
|
741
744
|
# dataset.select{|o| o.a, o.sum(:b)} # SELECT a, sum(b) FROM items
|
742
745
|
def select(*columns, &block)
|
743
|
-
columns += Array(
|
746
|
+
columns += Array(Sequel.virtual_row(&block)) if block
|
744
747
|
m = []
|
745
748
|
columns.map do |i|
|
746
749
|
i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
|
@@ -761,7 +764,8 @@ module Sequel
|
|
761
764
|
# dataset.select(:a).select(:b) # SELECT b FROM items
|
762
765
|
# dataset.select(:a).select_more(:b) # SELECT a, b FROM items
|
763
766
|
def select_more(*columns, &block)
|
764
|
-
|
767
|
+
columns = @opts[:select] + columns if @opts[:select]
|
768
|
+
select(*columns, &block)
|
765
769
|
end
|
766
770
|
|
767
771
|
# Formats a SELECT statement
|
@@ -769,9 +773,7 @@ module Sequel
|
|
769
773
|
# dataset.select_sql # => "SELECT * FROM items"
|
770
774
|
def select_sql
|
771
775
|
return static_sql(@opts[:sql]) if @opts[:sql]
|
772
|
-
|
773
|
-
select_clause_order.each{|x| send(:"select_#{x}_sql", sql)}
|
774
|
-
sql
|
776
|
+
clause_sql(:select)
|
775
777
|
end
|
776
778
|
|
777
779
|
# Same as select_sql, not aliased directly to make subclassing simpler.
|
@@ -845,32 +847,11 @@ module Sequel
|
|
845
847
|
# Raises an error if the dataset is grouped or includes more
|
846
848
|
# than one table.
|
847
849
|
def update_sql(values = {})
|
848
|
-
opts = @opts
|
849
|
-
|
850
850
|
return static_sql(opts[:sql]) if opts[:sql]
|
851
|
-
|
852
851
|
check_modification_allowed!
|
853
|
-
|
854
|
-
sql = "UPDATE #{source_list(@opts[:from])} SET "
|
855
|
-
set = if values.is_a?(Hash)
|
856
|
-
values = opts[:defaults].merge(values) if opts[:defaults]
|
857
|
-
values = values.merge(opts[:overrides]) if opts[:overrides]
|
858
|
-
# get values from hash
|
859
|
-
values.map do |k, v|
|
860
|
-
"#{[String, Symbol].any?{|c| k.is_a?(c)} ? quote_identifier(k) : literal(k)} = #{literal(v)}"
|
861
|
-
end.join(COMMA_SEPARATOR)
|
862
|
-
else
|
863
|
-
# copy values verbatim
|
864
|
-
values
|
865
|
-
end
|
866
|
-
sql << set
|
867
|
-
if where = opts[:where]
|
868
|
-
sql << " WHERE #{literal(where)}"
|
869
|
-
end
|
870
|
-
|
871
|
-
sql
|
852
|
+
clone(:values=>values)._update_sql
|
872
853
|
end
|
873
|
-
|
854
|
+
|
874
855
|
# Add a condition to the WHERE clause. See #filter for argument types.
|
875
856
|
#
|
876
857
|
# dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
|
@@ -933,12 +914,22 @@ module Sequel
|
|
933
914
|
end
|
934
915
|
|
935
916
|
[:inner, :full_outer, :right_outer, :left_outer].each do |jtype|
|
936
|
-
class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end")
|
917
|
+
class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end", __FILE__, __LINE__)
|
937
918
|
end
|
938
919
|
alias join inner_join
|
939
920
|
|
940
921
|
protected
|
941
922
|
|
923
|
+
# Formats in INSERT statement using the stored columns and values.
|
924
|
+
def _insert_sql
|
925
|
+
clause_sql(:insert)
|
926
|
+
end
|
927
|
+
|
928
|
+
# Formats an UPDATE statement using the stored values.
|
929
|
+
def _update_sql
|
930
|
+
clause_sql(:update)
|
931
|
+
end
|
932
|
+
|
942
933
|
# Return a from_self dataset if an order or limit is specified, so it works as expected
|
943
934
|
# with UNION, EXCEPT, and INTERSECT clauses.
|
944
935
|
def compound_from_self
|
@@ -955,6 +946,12 @@ module Sequel
|
|
955
946
|
clone(clause => cond)
|
956
947
|
end
|
957
948
|
|
949
|
+
# Formats the truncate statement. Assumes the table given has already been
|
950
|
+
# literalized.
|
951
|
+
def _truncate_sql(table)
|
952
|
+
"TRUNCATE TABLE #{table}"
|
953
|
+
end
|
954
|
+
|
958
955
|
# Do a simple join of the arguments (which should be strings or symbols) separated by commas
|
959
956
|
def argument_list(args)
|
960
957
|
args.join(COMMA_SEPARATOR)
|
@@ -972,6 +969,13 @@ module Sequel
|
|
972
969
|
raise(InvalidOperation, "Joined datasets cannot be modified") if (opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join]
|
973
970
|
end
|
974
971
|
|
972
|
+
# Prepare an SQL statement by calling all clause methods for the given statement type.
|
973
|
+
def clause_sql(type)
|
974
|
+
sql = type.to_s.upcase
|
975
|
+
send("#{type}_clause_methods").each{|x| send(x, sql)}
|
976
|
+
sql
|
977
|
+
end
|
978
|
+
|
975
979
|
# Converts an array of column names into a comma seperated string of
|
976
980
|
# column names. If the array is empty, a wildcard (*) is returned.
|
977
981
|
def column_list(columns)
|
@@ -989,6 +993,11 @@ module Sequel
|
|
989
993
|
:"#{DATASET_ALIAS_BASE_NAME}#{number}"
|
990
994
|
end
|
991
995
|
|
996
|
+
# The order of methods to call to build the DELETE SQL statement
|
997
|
+
def delete_clause_methods
|
998
|
+
DELETE_CLAUSE_METHODS
|
999
|
+
end
|
1000
|
+
|
992
1001
|
# Converts an array of expressions into a comma separated string of
|
993
1002
|
# expressions.
|
994
1003
|
def expression_list(columns)
|
@@ -1020,7 +1029,7 @@ module Sequel
|
|
1020
1029
|
SQL::BooleanExpression.new(:AND, *expr.map{|x| filter_expr(x)})
|
1021
1030
|
end
|
1022
1031
|
when Proc
|
1023
|
-
filter_expr(
|
1032
|
+
filter_expr(Sequel.virtual_row(&expr))
|
1024
1033
|
when SQL::NumericExpression, SQL::StringExpression
|
1025
1034
|
raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
|
1026
1035
|
when Symbol, SQL::Expression
|
@@ -1042,37 +1051,64 @@ module Sequel
|
|
1042
1051
|
v2 = Sequel.application_to_database_timestamp(v)
|
1043
1052
|
fmt = default_timestamp_format.gsub(/%[Nz]/) do |m|
|
1044
1053
|
if m == '%N'
|
1045
|
-
|
1054
|
+
format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*86400000000 : v.usec) if supports_timestamp_usecs?
|
1046
1055
|
else
|
1047
1056
|
if supports_timestamp_timezones?
|
1048
1057
|
# Would like to just use %z format, but it doesn't appear to work on Windows
|
1049
1058
|
# Instead, the offset fragment is constructed manually
|
1050
1059
|
minutes = (v2.is_a?(DateTime) ? v2.offset * 1440 : v2.utc_offset/60).to_i
|
1051
|
-
|
1060
|
+
format_timestamp_offset(*minutes.divmod(60))
|
1052
1061
|
end
|
1053
1062
|
end
|
1054
1063
|
end
|
1055
1064
|
v2.strftime(fmt)
|
1056
1065
|
end
|
1066
|
+
|
1067
|
+
# Return the SQL timestamp fragment to use for the timezone offset.
|
1068
|
+
def format_timestamp_offset(hour, minute)
|
1069
|
+
sprintf("%+03i%02i", hour, minute)
|
1070
|
+
end
|
1057
1071
|
|
1072
|
+
# Return the SQL timestamp fragment to use for the fractional time part.
|
1073
|
+
# Should start with the decimal point. Uses 6 decimal places by default.
|
1074
|
+
def format_timestamp_usec(usec)
|
1075
|
+
sprintf(".%06d", usec)
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
# SQL fragment specifying a list of identifiers
|
1058
1079
|
# SQL fragment specifying a list of identifiers
|
1059
1080
|
def identifier_list(columns)
|
1060
1081
|
columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
|
1061
1082
|
end
|
1062
1083
|
|
1063
|
-
# SQL
|
1064
|
-
def
|
1065
|
-
|
1084
|
+
# SQL fragment specifying the table to insert INTO
|
1085
|
+
def insert_into_sql(sql)
|
1086
|
+
sql << " INTO #{source_list(@opts[:from])}"
|
1066
1087
|
end
|
1067
1088
|
|
1068
|
-
#
|
1069
|
-
def
|
1070
|
-
|
1089
|
+
# The order of methods to call to build the INSERT SQL statement
|
1090
|
+
def insert_clause_methods
|
1091
|
+
INSERT_CLAUSE_METHODS
|
1071
1092
|
end
|
1072
1093
|
|
1073
|
-
# SQL
|
1074
|
-
def
|
1075
|
-
|
1094
|
+
# SQL fragment specifying the columns to insert into
|
1095
|
+
def insert_columns_sql(sql)
|
1096
|
+
columns = opts[:columns]
|
1097
|
+
sql << " (#{columns.join(COMMA_SEPARATOR)})" if columns && !columns.empty?
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
# SQL fragment specifying the values to insert.
|
1101
|
+
def insert_values_sql(sql)
|
1102
|
+
case values = opts[:values]
|
1103
|
+
when Array
|
1104
|
+
sql << (values.empty? ? " DEFAULT VALUES" : " VALUES #{literal(values)}")
|
1105
|
+
when Dataset
|
1106
|
+
sql << " #{subselect_sql(values)}"
|
1107
|
+
when LiteralString
|
1108
|
+
sql << " #{values}"
|
1109
|
+
else
|
1110
|
+
raise Error, "Unsupported INSERT values type, should be an Array or Dataset: #{values.inspect}"
|
1111
|
+
end
|
1076
1112
|
end
|
1077
1113
|
|
1078
1114
|
# Inverts the given order by breaking it into a list of column references
|
@@ -1156,7 +1192,9 @@ module Sequel
|
|
1156
1192
|
v.to_s
|
1157
1193
|
end
|
1158
1194
|
|
1159
|
-
# SQL fragmento for a type of object not handled by Dataset#literal.
|
1195
|
+
# SQL fragmento for a type of object not handled by Dataset#literal.
|
1196
|
+
# Raises an error. If a database specific type is allowed,
|
1197
|
+
# this should be overriden in a subclass.
|
1160
1198
|
def literal_other(v)
|
1161
1199
|
raise Error, "can't express #{v.inspect} as a SQL literal"
|
1162
1200
|
end
|
@@ -1262,8 +1300,8 @@ module Sequel
|
|
1262
1300
|
end
|
1263
1301
|
|
1264
1302
|
# The order of methods to call to build the SELECT SQL statement
|
1265
|
-
def
|
1266
|
-
|
1303
|
+
def select_clause_methods
|
1304
|
+
SELECT_CLAUSE_METHODS
|
1267
1305
|
end
|
1268
1306
|
|
1269
1307
|
# Modify the sql to add the columns selected
|
@@ -1293,6 +1331,7 @@ module Sequel
|
|
1293
1331
|
def select_from_sql(sql)
|
1294
1332
|
sql << " FROM #{source_list(@opts[:from])}" if @opts[:from]
|
1295
1333
|
end
|
1334
|
+
alias delete_from_sql select_from_sql
|
1296
1335
|
|
1297
1336
|
# Modify the sql to add the expressions to GROUP BY
|
1298
1337
|
def select_group_sql(sql)
|
@@ -1319,18 +1358,24 @@ module Sequel
|
|
1319
1358
|
def select_order_sql(sql)
|
1320
1359
|
sql << " ORDER BY #{expression_list(@opts[:order])}" if @opts[:order]
|
1321
1360
|
end
|
1361
|
+
alias delete_order_sql select_order_sql
|
1362
|
+
alias update_order_sql select_order_sql
|
1322
1363
|
|
1323
1364
|
# Modify the sql to add the filter criteria in the WHERE clause
|
1324
1365
|
def select_where_sql(sql)
|
1325
1366
|
sql << " WHERE #{literal(@opts[:where])}" if @opts[:where]
|
1326
1367
|
end
|
1368
|
+
alias delete_where_sql select_where_sql
|
1369
|
+
alias update_where_sql select_where_sql
|
1327
1370
|
|
1371
|
+
# SQL Fragment specifying the WITH clause
|
1328
1372
|
def select_with_sql(sql)
|
1329
1373
|
ws = opts[:with]
|
1330
1374
|
return if !ws || ws.empty?
|
1331
|
-
sql.replace("#{select_with_sql_base}#{ws.map{|w| "#{w[:name]}#{"(#{argument_list(w[:args])})" if w[:args]} AS
|
1375
|
+
sql.replace("#{select_with_sql_base}#{ws.map{|w| "#{quote_identifier(w[:name])}#{"(#{argument_list(w[:args])})" if w[:args]} AS #{literal_dataset(w[:dataset])}"}.join(COMMA_SEPARATOR)} #{sql}")
|
1332
1376
|
end
|
1333
1377
|
|
1378
|
+
# The base keyword to use for the SQL WITH clause
|
1334
1379
|
def select_with_sql_base
|
1335
1380
|
SQL_WITH
|
1336
1381
|
end
|
@@ -1376,10 +1421,31 @@ module Sequel
|
|
1376
1421
|
t.is_a?(String) ? quote_identifier(t) : literal(t)
|
1377
1422
|
end
|
1378
1423
|
|
1379
|
-
#
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1424
|
+
# The order of methods to call to build the UPDATE SQL statement
|
1425
|
+
def update_clause_methods
|
1426
|
+
UPDATE_CLAUSE_METHODS
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
# SQL fragment specifying the tables from with to delete
|
1430
|
+
def update_table_sql(sql)
|
1431
|
+
sql << " #{source_list(@opts[:from])}"
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
# The SQL fragment specifying the columns and values to SET.
|
1435
|
+
def update_set_sql(sql)
|
1436
|
+
values = opts[:values]
|
1437
|
+
set = if values.is_a?(Hash)
|
1438
|
+
values = opts[:defaults].merge(values) if opts[:defaults]
|
1439
|
+
values = values.merge(opts[:overrides]) if opts[:overrides]
|
1440
|
+
# get values from hash
|
1441
|
+
values.map do |k, v|
|
1442
|
+
"#{[String, Symbol].any?{|c| k.is_a?(c)} ? quote_identifier(k) : literal(k)} = #{literal(v)}"
|
1443
|
+
end.join(COMMA_SEPARATOR)
|
1444
|
+
else
|
1445
|
+
# copy values verbatim
|
1446
|
+
values
|
1447
|
+
end
|
1448
|
+
sql << " SET #{set}"
|
1383
1449
|
end
|
1384
1450
|
end
|
1385
1451
|
end
|