sequel 3.4.0 → 3.5.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 +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
|