sequel 2.11.0 → 2.12.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 +168 -0
- data/README.rdoc +77 -95
- data/Rakefile +100 -80
- data/bin/sequel +2 -1
- data/doc/advanced_associations.rdoc +23 -32
- data/doc/cheat_sheet.rdoc +23 -40
- data/doc/dataset_filtering.rdoc +6 -6
- data/doc/prepared_statements.rdoc +22 -22
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/schema.rdoc +3 -1
- data/doc/sharding.rdoc +8 -8
- data/doc/virtual_rows.rdoc +65 -0
- data/lib/sequel.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/ado.rb +3 -3
- data/lib/{sequel_core → sequel}/adapters/db2.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/dbi.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do.rb +9 -5
- data/lib/{sequel_core → sequel}/adapters/do/mysql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do/postgres.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do/sqlite.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/firebird.rb +84 -80
- data/lib/{sequel_core → sequel}/adapters/informix.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc.rb +21 -14
- data/lib/{sequel_core → sequel}/adapters/jdbc/h2.rb +14 -13
- data/lib/{sequel_core → sequel}/adapters/jdbc/mysql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/oracle.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/postgresql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/sqlite.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/mysql.rb +60 -39
- data/lib/{sequel_core → sequel}/adapters/odbc.rb +8 -4
- data/lib/{sequel_core → sequel}/adapters/openbase.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/oracle.rb +38 -7
- data/lib/{sequel_core → sequel}/adapters/postgres.rb +24 -24
- data/lib/{sequel_core → sequel}/adapters/shared/mssql.rb +5 -5
- data/lib/{sequel_core → sequel}/adapters/shared/mysql.rb +126 -71
- data/lib/{sequel_core → sequel}/adapters/shared/oracle.rb +7 -10
- data/lib/{sequel_core → sequel}/adapters/shared/postgres.rb +159 -125
- data/lib/{sequel_core → sequel}/adapters/shared/progress.rb +1 -2
- data/lib/{sequel_core → sequel}/adapters/shared/sqlite.rb +72 -67
- data/lib/{sequel_core → sequel}/adapters/sqlite.rb +11 -7
- data/lib/{sequel_core → sequel}/adapters/utils/date_format.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/utils/stored_procedures.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/utils/unsupported.rb +19 -0
- data/lib/{sequel_core → sequel}/connection_pool.rb +7 -5
- data/lib/sequel/core.rb +221 -0
- data/lib/{sequel_core → sequel}/core_sql.rb +91 -49
- data/lib/{sequel_core → sequel}/database.rb +264 -149
- data/lib/{sequel_core/schema/generator.rb → sequel/database/schema_generator.rb} +6 -2
- data/lib/{sequel_core/database/schema.rb → sequel/database/schema_methods.rb} +12 -12
- data/lib/sequel/database/schema_sql.rb +224 -0
- data/lib/{sequel_core → sequel}/dataset.rb +78 -236
- data/lib/{sequel_core → sequel}/dataset/convenience.rb +99 -61
- data/lib/{sequel_core/object_graph.rb → sequel/dataset/graph.rb} +16 -14
- data/lib/{sequel_core → sequel}/dataset/prepared_statements.rb +1 -1
- data/lib/{sequel_core → sequel}/dataset/sql.rb +150 -99
- data/lib/sequel/deprecated.rb +593 -0
- data/lib/sequel/deprecated_migration.rb +91 -0
- data/lib/sequel/exceptions.rb +48 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/{sequel_model → sequel/extensions}/inflector.rb +8 -1
- data/lib/{sequel_core → sequel/extensions}/migration.rb +1 -1
- data/lib/{sequel_core/dataset → sequel/extensions}/pagination.rb +0 -0
- data/lib/{sequel_core → sequel/extensions}/pretty_table.rb +7 -0
- data/lib/{sequel_core/dataset → sequel/extensions}/query.rb +7 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +43 -0
- data/lib/sequel/model.rb +110 -0
- data/lib/sequel/model/associations.rb +1300 -0
- data/lib/sequel/model/base.rb +937 -0
- data/lib/sequel/model/deprecated.rb +204 -0
- data/lib/sequel/model/deprecated_hooks.rb +103 -0
- data/lib/sequel/model/deprecated_inflector.rb +335 -0
- data/lib/sequel/model/deprecated_validations.rb +388 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/{sequel_model → sequel/model}/exceptions.rb +4 -4
- data/lib/sequel/model/inflections.rb +208 -0
- data/lib/sequel/model/plugins.rb +76 -0
- data/lib/sequel/plugins/caching.rb +122 -0
- data/lib/sequel/plugins/hook_class_methods.rb +122 -0
- data/lib/sequel/plugins/schema.rb +53 -0
- data/lib/sequel/plugins/serialization.rb +117 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +384 -0
- data/lib/sequel/plugins/validation_helpers.rb +150 -0
- data/lib/{sequel_core → sequel}/sql.rb +125 -190
- data/lib/{sequel_core → sequel}/version.rb +2 -1
- data/lib/sequel_core.rb +1 -172
- data/lib/sequel_model.rb +1 -91
- data/spec/adapters/firebird_spec.rb +5 -5
- data/spec/adapters/informix_spec.rb +1 -1
- data/spec/adapters/mysql_spec.rb +128 -42
- data/spec/adapters/oracle_spec.rb +47 -19
- data/spec/adapters/postgres_spec.rb +64 -52
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +12 -17
- data/spec/{sequel_core → core}/connection_pool_spec.rb +10 -10
- data/spec/{sequel_core → core}/core_ext_spec.rb +19 -19
- data/spec/{sequel_core → core}/core_sql_spec.rb +68 -71
- data/spec/{sequel_core → core}/database_spec.rb +135 -99
- data/spec/{sequel_core → core}/dataset_spec.rb +398 -242
- data/spec/{sequel_core → core}/expression_filters_spec.rb +13 -13
- data/spec/core/migration_spec.rb +263 -0
- data/spec/{sequel_core → core}/object_graph_spec.rb +10 -10
- data/spec/{sequel_core → core}/pretty_table_spec.rb +2 -2
- data/spec/{sequel_core → core}/schema_generator_spec.rb +0 -0
- data/spec/{sequel_core → core}/schema_spec.rb +8 -10
- data/spec/{sequel_core → core}/spec_helper.rb +29 -2
- data/spec/{sequel_core → core}/version_spec.rb +0 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/{sequel_model/hooks_spec.rb → extensions/hook_class_methods_spec.rb} +8 -23
- data/spec/{sequel_model → extensions}/inflector_spec.rb +3 -0
- data/spec/{sequel_core → extensions}/migration_spec.rb +4 -4
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/{sequel_model → extensions}/schema_spec.rb +22 -1
- data/spec/extensions/serialization_spec.rb +109 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/{sequel_model → extensions}/spec_helper.rb +13 -4
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/{sequel_model/validations_spec.rb → extensions/validation_class_methods_spec.rb} +15 -103
- data/spec/extensions/validation_helpers_spec.rb +291 -0
- data/spec/integration/dataset_test.rb +31 -0
- data/spec/integration/eager_loader_test.rb +17 -30
- data/spec/integration/schema_test.rb +8 -5
- data/spec/integration/spec_helper.rb +17 -0
- data/spec/integration/transaction_test.rb +68 -0
- data/spec/{sequel_model → model}/association_reflection_spec.rb +0 -0
- data/spec/{sequel_model → model}/associations_spec.rb +23 -10
- data/spec/{sequel_model → model}/base_spec.rb +29 -20
- data/spec/{sequel_model → model}/caching_spec.rb +16 -14
- data/spec/{sequel_model → model}/dataset_methods_spec.rb +0 -0
- data/spec/{sequel_model → model}/eager_loading_spec.rb +8 -8
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/{sequel_model → model}/model_spec.rb +25 -20
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/{sequel_model → model}/record_spec.rb +121 -62
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- metadata +136 -107
- data/lib/sequel_core/core_ext.rb +0 -217
- data/lib/sequel_core/dataset/callback.rb +0 -13
- data/lib/sequel_core/dataset/schema.rb +0 -15
- data/lib/sequel_core/deprecated.rb +0 -26
- data/lib/sequel_core/exceptions.rb +0 -44
- data/lib/sequel_core/schema.rb +0 -2
- data/lib/sequel_core/schema/sql.rb +0 -325
- data/lib/sequel_model/association_reflection.rb +0 -267
- data/lib/sequel_model/associations.rb +0 -499
- data/lib/sequel_model/base.rb +0 -539
- data/lib/sequel_model/caching.rb +0 -82
- data/lib/sequel_model/dataset_methods.rb +0 -26
- data/lib/sequel_model/eager_loading.rb +0 -370
- data/lib/sequel_model/hooks.rb +0 -101
- data/lib/sequel_model/plugins.rb +0 -62
- data/lib/sequel_model/record.rb +0 -568
- data/lib/sequel_model/schema.rb +0 -49
- data/lib/sequel_model/validations.rb +0 -429
- data/spec/sequel_model/plugins_spec.rb +0 -80
|
@@ -3,13 +3,19 @@ module Sequel
|
|
|
3
3
|
COMMA_SEPARATOR = ', '.freeze
|
|
4
4
|
COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
|
|
5
5
|
|
|
6
|
-
# Returns the first record matching the conditions.
|
|
6
|
+
# Returns the first record matching the conditions. Examples:
|
|
7
|
+
#
|
|
8
|
+
# ds[:id=>1] => {:id=1}
|
|
7
9
|
def [](*conditions)
|
|
10
|
+
Deprecation.deprecate('Using an Integer argument to Dataset#[] is deprecated and will raise an error in Sequel 3.0. Use Dataset#first.') if conditions.length == 1 and conditions.is_a?(Integer)
|
|
11
|
+
Deprecation.deprecate('Using Dataset#[] without an argument is deprecated and will raise an error in Sequel 3.0. Use Dataset#first.') if conditions.length == 0
|
|
8
12
|
first(*conditions)
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
# Update all records matching the conditions
|
|
12
|
-
# with the values specified.
|
|
16
|
+
# with the values specified. Examples:
|
|
17
|
+
#
|
|
18
|
+
# ds[:id=>1] = {:id=>2} # SQL: UPDATE ... SET id = 2 WHERE id = 1
|
|
13
19
|
def []=(conditions, values)
|
|
14
20
|
filter(conditions).update(values)
|
|
15
21
|
end
|
|
@@ -19,20 +25,18 @@ module Sequel
|
|
|
19
25
|
get{|o| o.avg(column)}
|
|
20
26
|
end
|
|
21
27
|
|
|
22
|
-
# Returns true if no records
|
|
28
|
+
# Returns true if no records exist in the dataset, false otherwise
|
|
23
29
|
def empty?
|
|
24
30
|
get(1).nil?
|
|
25
31
|
end
|
|
26
32
|
|
|
27
|
-
#
|
|
33
|
+
# If a integer argument is
|
|
28
34
|
# given, it is interpreted as a limit, and then returns all
|
|
29
35
|
# matching records up to that limit. If no argument is passed,
|
|
30
36
|
# it returns the first matching record. If any other type of
|
|
31
37
|
# argument(s) is passed, it is given to filter and the
|
|
32
38
|
# first matching record is returned. If a block is given, it is used
|
|
33
|
-
# to filter the dataset before returning anything.
|
|
34
|
-
#
|
|
35
|
-
# Examples:
|
|
39
|
+
# to filter the dataset before returning anything. Examples:
|
|
36
40
|
#
|
|
37
41
|
# ds.first => {:id=>7}
|
|
38
42
|
# ds.first(2) => [{:id=>6}, {:id=>4}]
|
|
@@ -61,16 +65,74 @@ module Sequel
|
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
# Return the column value for the first matching record in the dataset.
|
|
68
|
+
# Raises an error if both an argument and block is given.
|
|
69
|
+
#
|
|
70
|
+
# ds.get(:id)
|
|
71
|
+
# ds.get{|o| o.sum(:id)}
|
|
64
72
|
def get(column=nil, &block)
|
|
65
73
|
raise(Error, 'must provide argument or block to Dataset#get, not both') if column && block
|
|
66
74
|
(column ? select(column) : select(&block)).single_value
|
|
67
75
|
end
|
|
68
76
|
|
|
69
|
-
# Returns a dataset grouped by the given column with count by group
|
|
77
|
+
# Returns a dataset grouped by the given column with count by group,
|
|
78
|
+
# order by the count of records. Examples:
|
|
79
|
+
#
|
|
80
|
+
# ds.group_and_count(:name) => [{:name=>'a', :count=>1}, ...]
|
|
81
|
+
# ds.group_and_count(:first_name, :last_name) => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
|
|
70
82
|
def group_and_count(*columns)
|
|
71
83
|
group(*columns).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
|
|
72
84
|
end
|
|
73
85
|
|
|
86
|
+
# Inserts multiple records into the associated table. This method can be
|
|
87
|
+
# to efficiently insert a large amounts of records into a table. Inserts
|
|
88
|
+
# are automatically wrapped in a transaction.
|
|
89
|
+
#
|
|
90
|
+
# This method is called with a columns array and an array of value arrays:
|
|
91
|
+
#
|
|
92
|
+
# dataset.import([:x, :y], [[1, 2], [3, 4]])
|
|
93
|
+
#
|
|
94
|
+
# This method also accepts a dataset instead of an array of value arrays:
|
|
95
|
+
#
|
|
96
|
+
# dataset.import([:x, :y], other_dataset.select(:a___x, :b___y))
|
|
97
|
+
#
|
|
98
|
+
# The method also accepts a :slice or :commit_every option that specifies
|
|
99
|
+
# the number of records to insert per transaction. This is useful especially
|
|
100
|
+
# when inserting a large number of records, e.g.:
|
|
101
|
+
#
|
|
102
|
+
# # this will commit every 50 records
|
|
103
|
+
# dataset.import([:x, :y], [[1, 2], [3, 4], ...], :slice => 50)
|
|
104
|
+
def import(*args)
|
|
105
|
+
if args.empty?
|
|
106
|
+
Sequel::Deprecation.deprecate('Calling Sequel::Dataset#import with no arguments', 'Use dataset.multi_insert([])')
|
|
107
|
+
return
|
|
108
|
+
elsif args[0].is_a?(Array) && args[1].is_a?(Array)
|
|
109
|
+
columns, values, opts = *args
|
|
110
|
+
elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
|
|
111
|
+
table = @opts[:from].first
|
|
112
|
+
columns, dataset = *args
|
|
113
|
+
sql = "INSERT INTO #{quote_identifier(table)} (#{identifier_list(columns)}) VALUES #{literal(dataset)}"
|
|
114
|
+
return @db.transaction{execute_dui(sql)}
|
|
115
|
+
else
|
|
116
|
+
Sequel::Deprecation.deprecate('Calling Sequel::Dataset#import with hashes', 'Use Sequel::Dataset#multi_insert')
|
|
117
|
+
return multi_insert(*args)
|
|
118
|
+
end
|
|
119
|
+
# make sure there's work to do
|
|
120
|
+
Sequel::Deprecation.deprecate('Calling Sequel::Dataset#import an empty column array is deprecated and will raise an error in Sequel 3.0.') if columns.empty?
|
|
121
|
+
return if columns.empty? || values.empty?
|
|
122
|
+
|
|
123
|
+
slice_size = opts && (opts[:commit_every] || opts[:slice])
|
|
124
|
+
|
|
125
|
+
if slice_size
|
|
126
|
+
values.each_slice(slice_size) do |slice|
|
|
127
|
+
statements = multi_insert_sql(columns, slice)
|
|
128
|
+
@db.transaction(opts){statements.each{|st| execute_dui(st)}}
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
statements = multi_insert_sql(columns, values)
|
|
132
|
+
@db.transaction{statements.each{|st| execute_dui(st)}}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
74
136
|
# Returns the interval between minimum and maximum values for the given
|
|
75
137
|
# column.
|
|
76
138
|
def interval(column)
|
|
@@ -87,10 +149,15 @@ module Sequel
|
|
|
87
149
|
end
|
|
88
150
|
|
|
89
151
|
# Maps column values for each record in the dataset (if a column name is
|
|
90
|
-
# given), or performs the stock mapping functionality of Enumerable.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
152
|
+
# given), or performs the stock mapping functionality of Enumerable.
|
|
153
|
+
# Raises an error if both an argument and block are given. Examples:
|
|
154
|
+
#
|
|
155
|
+
# ds.map(:id) => [1, 2, 3, ...]
|
|
156
|
+
# ds.map{|r| r[:id] * 2} => [2, 4, 6, ...]
|
|
157
|
+
def map(column=nil, &block)
|
|
158
|
+
Deprecation.deprecate('Using Dataset#map with an argument and a block is deprecated and will raise an error in Sequel 3.0. Use an argument or a block, not both.') if column && block
|
|
159
|
+
if column
|
|
160
|
+
super(){|r| r[column]}
|
|
94
161
|
else
|
|
95
162
|
super(&block)
|
|
96
163
|
end
|
|
@@ -106,38 +173,23 @@ module Sequel
|
|
|
106
173
|
get{|o| o.min(column)}
|
|
107
174
|
end
|
|
108
175
|
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
# are automatically wrapped in a transaction.
|
|
112
|
-
#
|
|
113
|
-
# This method should be called with a columns array and an array of value arrays:
|
|
114
|
-
#
|
|
115
|
-
# dataset.multi_insert([:x, :y], [[1, 2], [3, 4]])
|
|
116
|
-
#
|
|
117
|
-
# This method can also be called with an array of hashes:
|
|
176
|
+
# This is a front end for import that allows you to submit an array of
|
|
177
|
+
# hashes instead of arrays of columns and values:
|
|
118
178
|
#
|
|
119
|
-
# dataset.multi_insert({:x => 1}, {:x => 2})
|
|
179
|
+
# dataset.multi_insert([{:x => 1}, {:x => 2}])
|
|
120
180
|
#
|
|
121
181
|
# Be aware that all hashes should have the same keys if you use this calling method,
|
|
122
182
|
# otherwise some columns could be missed or set to null instead of to default
|
|
123
183
|
# values.
|
|
124
184
|
#
|
|
125
|
-
#
|
|
126
|
-
# the number of records to insert per transaction. This is useful especially
|
|
127
|
-
# when inserting a large number of records, e.g.:
|
|
128
|
-
#
|
|
129
|
-
# # this will commit every 50 records
|
|
130
|
-
# dataset.multi_insert(lots_of_records, :slice => 50)
|
|
185
|
+
# You can also use the :slice or :commit_every option that import accepts.
|
|
131
186
|
def multi_insert(*args)
|
|
132
187
|
if args.empty?
|
|
188
|
+
Sequel::Deprecation.deprecate('Calling Sequel::Dataset#multi_insert with no arguments', 'Use dataset.multi_insert([])')
|
|
133
189
|
return
|
|
134
|
-
elsif args[0].is_a?(Array) && args[1].is_a?(Array)
|
|
135
|
-
columns
|
|
136
|
-
|
|
137
|
-
table = @opts[:from].first
|
|
138
|
-
columns, dataset = *args
|
|
139
|
-
sql = "INSERT INTO #{quote_identifier(table)} (#{identifier_list(columns)}) VALUES #{literal(dataset)}"
|
|
140
|
-
return @db.transaction{execute_dui(sql)}
|
|
190
|
+
elsif args[0].is_a?(Array) && (args[1].is_a?(Array) || args[1].is_a?(Dataset))
|
|
191
|
+
Sequel::Deprecation.deprecate('Calling Sequel::Dataset#multi_insert with an array of columns and an array of arrays of values', 'Use Sequel::Dataset#import')
|
|
192
|
+
return import(*args)
|
|
141
193
|
else
|
|
142
194
|
# we assume that an array of hashes is given
|
|
143
195
|
hashes, opts = *args
|
|
@@ -146,28 +198,9 @@ module Sequel
|
|
|
146
198
|
# convert the hashes into arrays
|
|
147
199
|
values = hashes.map {|h| columns.map {|c| h[c]}}
|
|
148
200
|
end
|
|
149
|
-
|
|
150
|
-
return if columns.empty? || values.empty?
|
|
151
|
-
|
|
152
|
-
slice_size = opts && (opts[:commit_every] || opts[:slice])
|
|
153
|
-
|
|
154
|
-
if slice_size
|
|
155
|
-
values.each_slice(slice_size) do |slice|
|
|
156
|
-
statements = multi_insert_sql(columns, slice)
|
|
157
|
-
@db.transaction{statements.each{|st| execute_dui(st)}}
|
|
158
|
-
end
|
|
159
|
-
else
|
|
160
|
-
statements = multi_insert_sql(columns, values)
|
|
161
|
-
@db.transaction{statements.each{|st| execute_dui(st)}}
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
alias_method :import, :multi_insert
|
|
165
|
-
|
|
166
|
-
# Pretty prints the records in the dataset as plain-text table.
|
|
167
|
-
def print(*cols)
|
|
168
|
-
Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
|
|
201
|
+
import(columns, values, opts)
|
|
169
202
|
end
|
|
170
|
-
|
|
203
|
+
|
|
171
204
|
# Returns a Range object made from the minimum and maximum values for the
|
|
172
205
|
# given column.
|
|
173
206
|
def range(column)
|
|
@@ -177,15 +210,20 @@ module Sequel
|
|
|
177
210
|
end
|
|
178
211
|
|
|
179
212
|
# Returns the first record in the dataset.
|
|
180
|
-
def single_record(opts = nil)
|
|
181
|
-
|
|
213
|
+
def single_record(opts = (defarg=true;nil))
|
|
214
|
+
Deprecation.deprecate("Calling Dataset#single_record with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).single_record.") unless defarg
|
|
215
|
+
ds = clone(:limit=>1)
|
|
216
|
+
opts = opts.merge(:limit=>1) if opts and opts[:limit]
|
|
217
|
+
defarg ? ds.each{|r| return r} : ds.each(opts){|r| return r}
|
|
182
218
|
nil
|
|
183
219
|
end
|
|
184
220
|
|
|
185
221
|
# Returns the first value of the first record in the dataset.
|
|
186
222
|
# Returns nil if dataset is empty.
|
|
187
|
-
def single_value(opts = nil)
|
|
188
|
-
|
|
223
|
+
def single_value(opts = (defarg=true;nil))
|
|
224
|
+
Deprecation.deprecate("Calling Dataset#single_value with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).single_value.") unless defarg
|
|
225
|
+
ds = naked.clone(:graph=>false)
|
|
226
|
+
if r = (defarg ? ds.single_record : ds.single_record(opts))
|
|
189
227
|
r.values.first
|
|
190
228
|
end
|
|
191
229
|
end
|
|
@@ -213,7 +251,7 @@ module Sequel
|
|
|
213
251
|
#
|
|
214
252
|
# This does not use a CSV library or handle quoting of values in
|
|
215
253
|
# any way. If any values in any of the rows could include commas or line
|
|
216
|
-
# endings, you
|
|
254
|
+
# endings, you shouldn't use this.
|
|
217
255
|
def to_csv(include_column_titles = true)
|
|
218
256
|
n = naked
|
|
219
257
|
cols = n.columns
|
|
@@ -9,7 +9,7 @@ module Sequel
|
|
|
9
9
|
# # CREATE TABLE artists (id INTEGER, name TEXT);
|
|
10
10
|
# # CREATE TABLE albums (id INTEGER, name TEXT, artist_id INTEGER);
|
|
11
11
|
# DB[:artists].left_outer_join(:albums, :artist_id=>:id).first
|
|
12
|
-
# => {:id=>
|
|
12
|
+
# => {:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}
|
|
13
13
|
# DB[:artists].graph(:albums, :artist_id=>:id).first
|
|
14
14
|
# => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>{:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}}
|
|
15
15
|
#
|
|
@@ -30,7 +30,7 @@ module Sequel
|
|
|
30
30
|
#
|
|
31
31
|
# Arguments:
|
|
32
32
|
# * dataset - Can be a symbol (specifying a table), another dataset,
|
|
33
|
-
# or an object that responds to .dataset and
|
|
33
|
+
# or an object that responds to .dataset and return a symbol or a dataset
|
|
34
34
|
# * join_conditions - Any condition(s) allowed by join_table.
|
|
35
35
|
# * options - A hash of graph options. The following options are currently used:
|
|
36
36
|
# * :implicit_qualifier - The qualifier of implicit conditions, see #join_table.
|
|
@@ -97,7 +97,7 @@ module Sequel
|
|
|
97
97
|
select = opts[:select] = []
|
|
98
98
|
columns.each do |column|
|
|
99
99
|
column_aliases[column] = [master, column]
|
|
100
|
-
select.push(
|
|
100
|
+
select.push(SQL::QualifiedIdentifier.new(master, column))
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
end
|
|
@@ -129,9 +129,9 @@ module Sequel
|
|
|
129
129
|
column_alias = :"#{column_alias}_#{column_alias_num}"
|
|
130
130
|
ca_num[column_alias] += 1
|
|
131
131
|
end
|
|
132
|
-
[column_alias,
|
|
132
|
+
[column_alias, SQL::QualifiedIdentifier.new(table_alias, column).as(column_alias)]
|
|
133
133
|
else
|
|
134
|
-
[column,
|
|
134
|
+
[column, SQL::QualifiedIdentifier.new(table_alias, column)]
|
|
135
135
|
end
|
|
136
136
|
column_aliases[col_alias] = [table_alias, column]
|
|
137
137
|
select.push(identifier)
|
|
@@ -147,14 +147,16 @@ module Sequel
|
|
|
147
147
|
# graphed dataset, and must be used instead of .select whenever
|
|
148
148
|
# graphing is used. Example:
|
|
149
149
|
#
|
|
150
|
-
# DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name]).first
|
|
151
|
-
# => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name}}
|
|
150
|
+
# DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name], :forty_two=>[:albums, :fourtwo, 42]).first
|
|
151
|
+
# => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name, :fourtwo=>42}}
|
|
152
152
|
#
|
|
153
153
|
# Arguments:
|
|
154
154
|
# * graph_aliases - Should be a hash with keys being symbols of
|
|
155
|
-
# column aliases, and values being arrays with two
|
|
156
|
-
# The first element of the array should be the table alias,
|
|
157
|
-
# and the second should be the actual column name.
|
|
155
|
+
# column aliases, and values being arrays with two or three elements.
|
|
156
|
+
# The first element of the array should be the table alias symbol,
|
|
157
|
+
# and the second should be the actual column name symbol. If the array
|
|
158
|
+
# has a third element, it is used as the value returned, instead of
|
|
159
|
+
# table_alias.column_name.
|
|
158
160
|
def set_graph_aliases(graph_aliases)
|
|
159
161
|
ds = select(*graph_alias_columns(graph_aliases))
|
|
160
162
|
ds.opts[:graph_aliases] = graph_aliases
|
|
@@ -175,7 +177,7 @@ module Sequel
|
|
|
175
177
|
# Transform the hash of graph aliases to an array of columns
|
|
176
178
|
def graph_alias_columns(graph_aliases)
|
|
177
179
|
graph_aliases.collect do |col_alias, tc|
|
|
178
|
-
identifier = tc[2] ||
|
|
180
|
+
identifier = tc[2] || SQL::QualifiedIdentifier.new(tc[0], tc[1])
|
|
179
181
|
identifier = SQL::AliasedExpression.new(identifier, col_alias) if tc[2] or tc[1] != col_alias
|
|
180
182
|
identifier
|
|
181
183
|
end
|
|
@@ -184,7 +186,7 @@ module Sequel
|
|
|
184
186
|
# Fetch the rows, split them into component table parts,
|
|
185
187
|
# tranform and run the row_proc on each part (if applicable),
|
|
186
188
|
# and yield a hash of the parts.
|
|
187
|
-
def graph_each(opts, &block)
|
|
189
|
+
def graph_each(opts=(defarg=true;nil), &block)
|
|
188
190
|
# Reject tables with nil datasets, as they are excluded from
|
|
189
191
|
# the result set
|
|
190
192
|
datasets = @opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
|
|
@@ -198,7 +200,7 @@ module Sequel
|
|
|
198
200
|
# Use the manually set graph aliases, if any, otherwise
|
|
199
201
|
# use the ones automatically created by .graph
|
|
200
202
|
column_aliases = @opts[:graph_aliases] || @opts[:graph][:column_aliases]
|
|
201
|
-
fetch_rows(select_sql(opts)) do |r|
|
|
203
|
+
fetch_rows(defarg ? select_sql : select_sql(opts)) do |r|
|
|
202
204
|
graph = {}
|
|
203
205
|
# Create the sub hashes, one per table
|
|
204
206
|
table_aliases.each{|ta| graph[ta]={}}
|
|
@@ -213,7 +215,7 @@ module Sequel
|
|
|
213
215
|
# row_proc if applicable
|
|
214
216
|
datasets.each do |ta,ds,tr,rp|
|
|
215
217
|
g = graph[ta]
|
|
216
|
-
graph[ta] = if g.values.any?
|
|
218
|
+
graph[ta] = if g.values.any?{|x| !x.nil?}
|
|
217
219
|
g = ds.transform_load(g) if tr
|
|
218
220
|
g = rp[g] if rp
|
|
219
221
|
g
|
|
@@ -7,6 +7,8 @@ module Sequel
|
|
|
7
7
|
COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
|
|
8
8
|
COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
|
|
9
9
|
COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
|
|
10
|
+
IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
|
|
11
|
+
IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
|
|
10
12
|
N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
|
|
11
13
|
NULL = "NULL".freeze
|
|
12
14
|
QUESTION_MARK = '?'.freeze
|
|
@@ -18,8 +20,10 @@ module Sequel
|
|
|
18
20
|
# Adds an further filter to an existing filter using AND. If no filter
|
|
19
21
|
# exists an error is raised. This method is identical to #filter except
|
|
20
22
|
# it expects an existing filter.
|
|
23
|
+
#
|
|
24
|
+
# ds.filter(:a).and(:b) # SQL: WHERE a AND b
|
|
21
25
|
def and(*cond, &block)
|
|
22
|
-
raise(
|
|
26
|
+
raise(InvalidOperation, "No existing filter found.") unless @opts[:having] || @opts[:where]
|
|
23
27
|
filter(*cond, &block)
|
|
24
28
|
end
|
|
25
29
|
|
|
@@ -56,6 +60,9 @@ module Sequel
|
|
|
56
60
|
# SQL fragment for complex expressions
|
|
57
61
|
def complex_expression_sql(op, args)
|
|
58
62
|
case op
|
|
63
|
+
when *IS_OPERATORS
|
|
64
|
+
v = IS_LITERALS[args.at(1)] || raise(Error, 'Invalid argument used for IS operator')
|
|
65
|
+
"(#{literal(args.at(0))} #{op} #{v})"
|
|
59
66
|
when *TWO_ARITY_OPERATORS
|
|
60
67
|
"(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
|
|
61
68
|
when *N_ARITY_OPERATORS
|
|
@@ -73,23 +80,23 @@ module Sequel
|
|
|
73
80
|
|
|
74
81
|
# Returns the number of records in the dataset.
|
|
75
82
|
def count
|
|
76
|
-
options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count :
|
|
83
|
+
options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count : clone(STOCK_COUNT_OPTS).single_value.to_i
|
|
77
84
|
end
|
|
78
|
-
alias_method :size, :count
|
|
79
85
|
|
|
80
86
|
# Formats a DELETE statement using the given options and dataset options.
|
|
81
87
|
#
|
|
82
88
|
# dataset.filter{|o| o.price >= 100}.delete_sql #=>
|
|
83
89
|
# "DELETE FROM items WHERE (price >= 100)"
|
|
84
|
-
def delete_sql(opts = nil)
|
|
90
|
+
def delete_sql(opts = (defarg=true;nil))
|
|
91
|
+
Deprecation.deprecate("Calling Dataset#delete_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).delete_sql.") unless defarg
|
|
85
92
|
opts = opts ? @opts.merge(opts) : @opts
|
|
86
93
|
|
|
87
94
|
return static_sql(opts[:sql]) if opts[:sql]
|
|
88
95
|
|
|
89
96
|
if opts[:group]
|
|
90
|
-
raise
|
|
97
|
+
raise InvalidOperation, "Grouped datasets cannot be deleted from"
|
|
91
98
|
elsif opts[:from].is_a?(Array) && opts[:from].size > 1
|
|
92
|
-
raise
|
|
99
|
+
raise InvalidOperation, "Joined datasets cannot be deleted from"
|
|
93
100
|
end
|
|
94
101
|
|
|
95
102
|
sql = "DELETE FROM #{source_list(opts[:from])}"
|
|
@@ -101,6 +108,18 @@ module Sequel
|
|
|
101
108
|
sql
|
|
102
109
|
end
|
|
103
110
|
|
|
111
|
+
# Returns a copy of the dataset with the SQL DISTINCT clause.
|
|
112
|
+
# The DISTINCT clause is used to remove duplicate rows from the
|
|
113
|
+
# output. If arguments are provided, uses a DISTINCT ON clause,
|
|
114
|
+
# in which case it will only be distinct on those columns, instead
|
|
115
|
+
# of all returned columns.
|
|
116
|
+
#
|
|
117
|
+
# dataset.distinct # SQL: SELECT DISTINCT * FROM items
|
|
118
|
+
# dataset.order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
|
|
119
|
+
def distinct(*args)
|
|
120
|
+
clone(:distinct => args)
|
|
121
|
+
end
|
|
122
|
+
|
|
104
123
|
# Adds an EXCEPT clause using a second dataset object. If all is true the
|
|
105
124
|
# clause used is EXCEPT ALL, which may return duplicate rows.
|
|
106
125
|
#
|
|
@@ -117,7 +136,7 @@ module Sequel
|
|
|
117
136
|
def exclude(*cond, &block)
|
|
118
137
|
clause = (@opts[:having] ? :having : :where)
|
|
119
138
|
cond = cond.first if cond.size == 1
|
|
120
|
-
cond =
|
|
139
|
+
cond = SQL::BooleanExpression.from_value_pairs(cond, :OR) if Sequel.condition_specifier?(cond)
|
|
121
140
|
cond = filter_expr(cond, &block)
|
|
122
141
|
cond = SQL::BooleanExpression.invert(cond)
|
|
123
142
|
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
|
|
@@ -128,17 +147,18 @@ module Sequel
|
|
|
128
147
|
#
|
|
129
148
|
# DB.select(1).where(DB[:items].exists).sql
|
|
130
149
|
# #=> "SELECT 1 WHERE EXISTS (SELECT * FROM items)"
|
|
131
|
-
def exists(opts = nil)
|
|
132
|
-
|
|
150
|
+
def exists(opts = (defarg=true;nil))
|
|
151
|
+
Deprecation.deprecate("Calling Dataset#exists with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).exists.") unless defarg
|
|
152
|
+
LiteralString.new("EXISTS (#{defarg ? select_sql : select_sql(opts)})")
|
|
133
153
|
end
|
|
134
154
|
|
|
135
155
|
# Returns a copy of the dataset with the given conditions imposed upon it.
|
|
136
|
-
# If the query has
|
|
137
|
-
# HAVING clause. If not, then they are imposed in the WHERE clause.
|
|
156
|
+
# If the query already has a HAVING clause, then the conditions are imposed in the
|
|
157
|
+
# HAVING clause. If not, then they are imposed in the WHERE clause.
|
|
138
158
|
#
|
|
139
159
|
# filter accepts the following argument types:
|
|
140
160
|
#
|
|
141
|
-
# * Hash - list of equality expressions
|
|
161
|
+
# * Hash - list of equality/inclusion expressions
|
|
142
162
|
# * Array - depends:
|
|
143
163
|
# * If first member is a string, assumes the rest of the arguments
|
|
144
164
|
# are parameters and interpolates them into the string.
|
|
@@ -148,10 +168,13 @@ module Sequel
|
|
|
148
168
|
# * String - taken literally
|
|
149
169
|
# * Symbol - taken as a boolean column argument (e.g. WHERE active)
|
|
150
170
|
# * Sequel::SQL::BooleanExpression - an existing condition expression,
|
|
151
|
-
# probably created using the Sequel
|
|
171
|
+
# probably created using the Sequel expression filter DSL.
|
|
152
172
|
#
|
|
153
173
|
# filter also takes a block, which should return one of the above argument
|
|
154
|
-
# types, and is treated the same way.
|
|
174
|
+
# types, and is treated the same way. This block yields a virtual row object,
|
|
175
|
+
# which is easy to use to create identifiers and functions.
|
|
176
|
+
#
|
|
177
|
+
# If both a block and regular argument
|
|
155
178
|
# are provided, they get ANDed together.
|
|
156
179
|
#
|
|
157
180
|
# Examples:
|
|
@@ -177,14 +200,8 @@ module Sequel
|
|
|
177
200
|
#
|
|
178
201
|
# See doc/dataset_filters.rdoc for more examples and details.
|
|
179
202
|
def filter(*cond, &block)
|
|
180
|
-
|
|
181
|
-
cond = cond.first if cond.size == 1
|
|
182
|
-
cond = transform_save(cond) if @transform if cond.is_a?(Hash)
|
|
183
|
-
cond = filter_expr(cond, &block)
|
|
184
|
-
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause] && !@opts[clause].blank?
|
|
185
|
-
clone(clause => cond)
|
|
203
|
+
_filter(@opts[:having] ? :having : :where, *cond, &block)
|
|
186
204
|
end
|
|
187
|
-
alias_method :where, :filter
|
|
188
205
|
|
|
189
206
|
# The first source (primary table) for this dataset. If the dataset doesn't
|
|
190
207
|
# have a table, raises an error. If the table is aliased, returns the aliased name.
|
|
@@ -205,6 +222,9 @@ module Sequel
|
|
|
205
222
|
end
|
|
206
223
|
|
|
207
224
|
# Returns a copy of the dataset with the source changed.
|
|
225
|
+
#
|
|
226
|
+
# dataset.from(:blah) # SQL: SELECT * FROM blah
|
|
227
|
+
# dataset.from(:blah, :foo) # SQL: SELECT * FROM blah, foo
|
|
208
228
|
def from(*source)
|
|
209
229
|
clone(:from => source)
|
|
210
230
|
end
|
|
@@ -232,22 +252,30 @@ module Sequel
|
|
|
232
252
|
# in some databases). See Sequel::SQL::StringExpression.like. Note that the
|
|
233
253
|
# total number of pattern matches will be cols.length * terms.length,
|
|
234
254
|
# which could cause performance issues.
|
|
255
|
+
#
|
|
256
|
+
# dataset.grep(:a, '%test%') # SQL: SELECT * FROM items WHERE a LIKE '%test%'
|
|
257
|
+
# dataset.grep([:a, :b], %w'%test% foo') # SQL: SELECT * FROM items WHERE a LIKE '%test%' OR a LIKE 'foo' OR b LIKE '%test%' OR b LIKE 'foo'
|
|
235
258
|
def grep(cols, terms)
|
|
236
259
|
filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
|
|
237
260
|
end
|
|
238
261
|
|
|
239
262
|
# Returns a copy of the dataset with the results grouped by the value of
|
|
240
|
-
# the given columns
|
|
263
|
+
# the given columns.
|
|
264
|
+
#
|
|
265
|
+
# dataset.group(:id) # SELECT * FROM items GROUP BY id
|
|
266
|
+
# dataset.group(:id, :name) # SELECT * FROM items GROUP BY id, name
|
|
241
267
|
def group(*columns)
|
|
242
268
|
clone(:group => columns)
|
|
243
269
|
end
|
|
244
|
-
|
|
270
|
+
alias group_by group
|
|
245
271
|
|
|
246
|
-
# Returns a copy of the dataset with the
|
|
247
|
-
# an error if the dataset has not been grouped. See
|
|
272
|
+
# Returns a copy of the dataset with the HAVING conditions changed. Raises
|
|
273
|
+
# an error if the dataset has not been grouped. See #filter for argument types.
|
|
274
|
+
#
|
|
275
|
+
# dataset.group(:sum).having(:sum=>10) # SQL: SELECT * FROM items GROUP BY sum HAVING sum = 10
|
|
248
276
|
def having(*cond, &block)
|
|
249
|
-
raise(
|
|
250
|
-
|
|
277
|
+
raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
|
|
278
|
+
_filter(:having, *cond, &block)
|
|
251
279
|
end
|
|
252
280
|
|
|
253
281
|
# Inserts multiple values. If a block is given it is invoked for each
|
|
@@ -266,7 +294,7 @@ module Sequel
|
|
|
266
294
|
# the resulting statement includes column names. If no values are given,
|
|
267
295
|
# the resulting statement includes a DEFAULT VALUES clause.
|
|
268
296
|
#
|
|
269
|
-
# dataset.insert_sql
|
|
297
|
+
# dataset.insert_sql #=> 'INSERT INTO items DEFAULT VALUES'
|
|
270
298
|
# dataset.insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
|
271
299
|
# dataset.insert_sql(:a => 1, :b => 2) #=>
|
|
272
300
|
# 'INSERT INTO items (a, b) VALUES (1, 2)'
|
|
@@ -279,7 +307,7 @@ module Sequel
|
|
|
279
307
|
values = {}
|
|
280
308
|
when 1
|
|
281
309
|
vals = values.at(0)
|
|
282
|
-
if
|
|
310
|
+
if [Hash, Dataset, Array].any?{|c| vals.is_a?(c)}
|
|
283
311
|
values = vals
|
|
284
312
|
elsif vals.respond_to?(:values)
|
|
285
313
|
values = vals.values
|
|
@@ -360,7 +388,7 @@ module Sequel
|
|
|
360
388
|
|
|
361
389
|
# Returns a joined dataset. Uses the following arguments:
|
|
362
390
|
#
|
|
363
|
-
# * type - The type of join to do (:inner
|
|
391
|
+
# * type - The type of join to do (e.g. :inner)
|
|
364
392
|
# * table - Depends on type:
|
|
365
393
|
# * Dataset - a subselect is performed with an alias of tN for some value of N
|
|
366
394
|
# * Model (or anything responding to :table_name) - table.table_name
|
|
@@ -389,7 +417,7 @@ module Sequel
|
|
|
389
417
|
# the table alias/name for the last joined (or first table), and an array of previous
|
|
390
418
|
# SQL::JoinClause.
|
|
391
419
|
def join_table(type, table, expr=nil, options={}, &block)
|
|
392
|
-
if options.
|
|
420
|
+
if [Symbol, String].any?{|c| options.is_a?(c)}
|
|
393
421
|
table_alias = options
|
|
394
422
|
last_alias = nil
|
|
395
423
|
else
|
|
@@ -414,7 +442,7 @@ module Sequel
|
|
|
414
442
|
SQL::JoinUsingClause.new(expr, type, table, table_alias)
|
|
415
443
|
else
|
|
416
444
|
last_alias ||= @opts[:last_joined_table] || (first_source.is_a?(Dataset) ? 't1' : first_source)
|
|
417
|
-
if
|
|
445
|
+
if Sequel.condition_specifier?(expr)
|
|
418
446
|
expr = expr.collect do |k, v|
|
|
419
447
|
k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
|
|
420
448
|
v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
|
|
@@ -436,12 +464,15 @@ module Sequel
|
|
|
436
464
|
# If given an integer, the dataset will contain only the first l results.
|
|
437
465
|
# If given a range, it will contain only those at offsets within that
|
|
438
466
|
# range. If a second argument is given, it is used as an offset.
|
|
467
|
+
#
|
|
468
|
+
# dataset.limit(10) # SQL: SELECT * FROM items LIMIT 10
|
|
469
|
+
# dataset.limit(10, 20) # SQL: SELECT * FROM items LIMIT 10 OFFSET 20
|
|
439
470
|
def limit(l, o = nil)
|
|
440
471
|
return from_self.limit(l, o) if @opts[:sql]
|
|
441
472
|
|
|
442
473
|
if Range === l
|
|
443
474
|
o = l.first
|
|
444
|
-
l = l.
|
|
475
|
+
l = l.last - l.first + (l.exclude_end? ? 0 : 1)
|
|
445
476
|
end
|
|
446
477
|
l = l.to_i
|
|
447
478
|
raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
|
|
@@ -509,28 +540,25 @@ module Sequel
|
|
|
509
540
|
# This method should be overridden by descendants if the support
|
|
510
541
|
# inserting multiple records in a single SQL statement.
|
|
511
542
|
def multi_insert_sql(columns, values)
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
values.map do |r|
|
|
515
|
-
"INSERT INTO #{table} (#{columns}) VALUES #{literal(r)}"
|
|
516
|
-
end
|
|
543
|
+
s = "INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES "
|
|
544
|
+
values.map{|r| s + literal(r)}
|
|
517
545
|
end
|
|
518
546
|
|
|
519
547
|
# Adds an alternate filter to an existing filter using OR. If no filter
|
|
520
548
|
# exists an error is raised.
|
|
549
|
+
#
|
|
550
|
+
# dataset.filter(:a).or(:b) # SQL: SELECT * FROM items WHERE a OR b
|
|
521
551
|
def or(*cond, &block)
|
|
522
552
|
clause = (@opts[:having] ? :having : :where)
|
|
553
|
+
raise(InvalidOperation, "No existing filter found.") unless @opts[clause]
|
|
523
554
|
cond = cond.first if cond.size == 1
|
|
524
|
-
|
|
525
|
-
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
|
|
526
|
-
else
|
|
527
|
-
raise Error::NoExistingFilter, "No existing filter found."
|
|
528
|
-
end
|
|
555
|
+
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
|
|
529
556
|
end
|
|
530
557
|
|
|
531
558
|
# Returns a copy of the dataset with the order changed. If a nil is given
|
|
532
559
|
# the returned dataset has no order. This can accept multiple arguments
|
|
533
|
-
# of varying kinds, and even SQL functions.
|
|
560
|
+
# of varying kinds, and even SQL functions. If a block is given, it is treated
|
|
561
|
+
# as a virtual row block, similar to filter.
|
|
534
562
|
#
|
|
535
563
|
# ds.order(:name).sql #=> 'SELECT * FROM items ORDER BY name'
|
|
536
564
|
# ds.order(:a, :b).sql #=> 'SELECT * FROM items ORDER BY a, b'
|
|
@@ -538,19 +566,21 @@ module Sequel
|
|
|
538
566
|
# ds.order(:a + :b).sql #=> 'SELECT * FROM items ORDER BY (a + b)'
|
|
539
567
|
# ds.order(:name.desc).sql #=> 'SELECT * FROM items ORDER BY name DESC'
|
|
540
568
|
# ds.order(:name.asc).sql #=> 'SELECT * FROM items ORDER BY name ASC'
|
|
541
|
-
# ds.order(:
|
|
569
|
+
# ds.order{|o| o.sum(:name)}.sql #=> 'SELECT * FROM items ORDER BY sum(name)'
|
|
542
570
|
# ds.order(nil).sql #=> 'SELECT * FROM items'
|
|
543
|
-
def order(*columns)
|
|
544
|
-
columns += Array((
|
|
571
|
+
def order(*columns, &block)
|
|
572
|
+
columns += Array(virtual_row_block_call(block)) if block
|
|
545
573
|
clone(:order => (columns.compact.empty?) ? nil : columns)
|
|
546
574
|
end
|
|
547
575
|
alias_method :order_by, :order
|
|
548
576
|
|
|
549
577
|
# Returns a copy of the dataset with the order columns added
|
|
550
578
|
# to the existing order.
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
579
|
+
#
|
|
580
|
+
# ds.order(:a).order(:b).sql #=> 'SELECT * FROM items ORDER BY b'
|
|
581
|
+
# ds.order(:a).order_more(:b).sql #=> 'SELECT * FROM items ORDER BY a, b'
|
|
582
|
+
def order_more(*columns, &block)
|
|
583
|
+
order(*Array(@opts[:order]).concat(columns), &block)
|
|
554
584
|
end
|
|
555
585
|
|
|
556
586
|
# SQL fragment for the ordered expression, used in the ORDER BY
|
|
@@ -570,7 +600,7 @@ module Sequel
|
|
|
570
600
|
# SQL fragment for the qualifed identifier, specifying
|
|
571
601
|
# a table and a column (or schema and table).
|
|
572
602
|
def qualified_identifier_sql(qcr)
|
|
573
|
-
[qcr.table, qcr.column].map{|x|
|
|
603
|
+
[qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
|
|
574
604
|
end
|
|
575
605
|
|
|
576
606
|
# Adds quoting to identifiers (columns and tables). If identifiers are not
|
|
@@ -582,7 +612,6 @@ module Sequel
|
|
|
582
612
|
name = quoted_identifier(name) if quote_identifiers?
|
|
583
613
|
name
|
|
584
614
|
end
|
|
585
|
-
alias_method :quote_column_ref, :quote_identifier
|
|
586
615
|
|
|
587
616
|
# Separates the schema from the table and returns a string with them
|
|
588
617
|
# quoted (if quoting identifiers)
|
|
@@ -603,7 +632,7 @@ module Sequel
|
|
|
603
632
|
def reverse_order(*order)
|
|
604
633
|
order(*invert_order(order.empty? ? @opts[:order] : order))
|
|
605
634
|
end
|
|
606
|
-
|
|
635
|
+
alias reverse reverse_order
|
|
607
636
|
|
|
608
637
|
# Split the schema information from the table
|
|
609
638
|
def schema_and_table(table_name)
|
|
@@ -624,27 +653,38 @@ module Sequel
|
|
|
624
653
|
end
|
|
625
654
|
|
|
626
655
|
# Returns a copy of the dataset with the columns selected changed
|
|
627
|
-
# to the given columns.
|
|
628
|
-
|
|
629
|
-
|
|
656
|
+
# to the given columns. This also takes a virtual row block,
|
|
657
|
+
# similar to filter.
|
|
658
|
+
#
|
|
659
|
+
# dataset.select(:a) # SELECT a FROM items
|
|
660
|
+
# dataset.select(:a, :b) # SELECT a, b FROM items
|
|
661
|
+
# dataset.select{|o| o.a, o.sum(:b)} # SELECT a, sum(b) FROM items
|
|
662
|
+
def select(*columns, &block)
|
|
663
|
+
columns += Array(virtual_row_block_call(block)) if block
|
|
630
664
|
clone(:select => columns)
|
|
631
665
|
end
|
|
632
666
|
|
|
633
667
|
# Returns a copy of the dataset selecting the wildcard.
|
|
668
|
+
#
|
|
669
|
+
# dataset.select(:a).select_all # SELECT * FROM items
|
|
634
670
|
def select_all
|
|
635
671
|
clone(:select => nil)
|
|
636
672
|
end
|
|
637
673
|
|
|
638
674
|
# Returns a copy of the dataset with the given columns added
|
|
639
675
|
# to the existing selected columns.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
676
|
+
#
|
|
677
|
+
# dataset.select(:a).select(:b) # SELECT b FROM items
|
|
678
|
+
# dataset.select(:a).select_more(:b) # SELECT a, b FROM items
|
|
679
|
+
def select_more(*columns, &block)
|
|
680
|
+
select(*Array(@opts[:select]).concat(columns), &block)
|
|
643
681
|
end
|
|
644
682
|
|
|
645
|
-
# Formats a SELECT statement
|
|
646
|
-
#
|
|
647
|
-
|
|
683
|
+
# Formats a SELECT statement
|
|
684
|
+
#
|
|
685
|
+
# dataset.select_sql # => "SELECT * FROM items"
|
|
686
|
+
def select_sql(opts = (defarg=true;nil))
|
|
687
|
+
Deprecation.deprecate("Calling Dataset#select_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).select_sql.") unless defarg
|
|
648
688
|
opts = opts ? @opts.merge(opts) : @opts
|
|
649
689
|
return static_sql(opts[:sql]) if opts[:sql]
|
|
650
690
|
sql = 'SELECT'
|
|
@@ -653,8 +693,9 @@ module Sequel
|
|
|
653
693
|
end
|
|
654
694
|
|
|
655
695
|
# Same as select_sql, not aliased directly to make subclassing simpler.
|
|
656
|
-
def sql(
|
|
657
|
-
select_sql(
|
|
696
|
+
def sql(opts = (defarg=true;nil))
|
|
697
|
+
Deprecation.deprecate("Calling Dataset#select_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).select_sql.") unless defarg
|
|
698
|
+
defarg ? select_sql : select_sql(opts)
|
|
658
699
|
end
|
|
659
700
|
|
|
660
701
|
# SQL fragment for specifying subscripts (SQL arrays)
|
|
@@ -662,21 +703,9 @@ module Sequel
|
|
|
662
703
|
"#{s.f}[#{s.sub.join(COMMA_SEPARATOR)}]"
|
|
663
704
|
end
|
|
664
705
|
|
|
665
|
-
# Converts a symbol into a column name. This method supports underscore
|
|
666
|
-
# notation in order to express qualified (two underscores) and aliased
|
|
667
|
-
# (three underscores) columns:
|
|
668
|
-
#
|
|
669
|
-
# ds = DB[:items]
|
|
670
|
-
# :abc.to_column_ref(ds) #=> "abc"
|
|
671
|
-
# :abc___a.to_column_ref(ds) #=> "abc AS a"
|
|
672
|
-
# :items__abc.to_column_ref(ds) #=> "items.abc"
|
|
673
|
-
# :items__abc___a.to_column_ref(ds) #=> "items.abc AS a"
|
|
674
|
-
#
|
|
675
|
-
def symbol_to_column_ref(sym)
|
|
676
|
-
literal_symbol(sym)
|
|
677
|
-
end
|
|
678
|
-
|
|
679
706
|
# Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
|
|
707
|
+
#
|
|
708
|
+
# dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items
|
|
680
709
|
def unfiltered
|
|
681
710
|
clone(:where => nil, :having => nil)
|
|
682
711
|
end
|
|
@@ -690,13 +719,9 @@ module Sequel
|
|
|
690
719
|
compound_clone(:union, dataset, all)
|
|
691
720
|
end
|
|
692
721
|
|
|
693
|
-
# Returns a copy of the dataset with the distinct option.
|
|
694
|
-
def uniq(*args)
|
|
695
|
-
clone(:distinct => args)
|
|
696
|
-
end
|
|
697
|
-
alias_method :distinct, :uniq
|
|
698
|
-
|
|
699
722
|
# Returns a copy of the dataset with no order.
|
|
723
|
+
#
|
|
724
|
+
# dataset.order(:a).unordered # SELECT * FROM items
|
|
700
725
|
def unordered
|
|
701
726
|
order(nil)
|
|
702
727
|
end
|
|
@@ -706,19 +731,18 @@ module Sequel
|
|
|
706
731
|
# dataset.update_sql(:price => 100, :category => 'software') #=>
|
|
707
732
|
# "UPDATE items SET price = 100, category = 'software'"
|
|
708
733
|
#
|
|
709
|
-
# Accepts a block, but such usage is discouraged.
|
|
710
|
-
#
|
|
711
734
|
# Raises an error if the dataset is grouped or includes more
|
|
712
735
|
# than one table.
|
|
713
|
-
def update_sql(values = {}, opts = nil)
|
|
736
|
+
def update_sql(values = {}, opts = (defarg=true;nil))
|
|
737
|
+
Deprecation.deprecate("Calling Dataset#update_sql with an argument is deprecated and will raise an error in Sequel 3.0. Use dataset.clone(opts).update_sql.") unless defarg
|
|
714
738
|
opts = opts ? @opts.merge(opts) : @opts
|
|
715
739
|
|
|
716
740
|
return static_sql(opts[:sql]) if opts[:sql]
|
|
717
741
|
|
|
718
742
|
if opts[:group]
|
|
719
|
-
raise
|
|
743
|
+
raise InvalidOperation, "A grouped dataset cannot be updated"
|
|
720
744
|
elsif (opts[:from].size > 1) or opts[:join]
|
|
721
|
-
raise
|
|
745
|
+
raise InvalidOperation, "A joined dataset cannot be updated"
|
|
722
746
|
end
|
|
723
747
|
|
|
724
748
|
sql = "UPDATE #{source_list(@opts[:from])} SET "
|
|
@@ -728,7 +752,7 @@ module Sequel
|
|
|
728
752
|
# get values from hash
|
|
729
753
|
values = transform_save(values) if @transform
|
|
730
754
|
values.map do |k, v|
|
|
731
|
-
"#{k.
|
|
755
|
+
"#{[String, Symbol].any?{|c| k.is_a?(c)} ? quote_identifier(k) : literal(k)} = #{literal(v)}"
|
|
732
756
|
end.join(COMMA_SEPARATOR)
|
|
733
757
|
else
|
|
734
758
|
# copy values verbatim
|
|
@@ -742,16 +766,27 @@ module Sequel
|
|
|
742
766
|
sql
|
|
743
767
|
end
|
|
744
768
|
|
|
769
|
+
# Add a condition to the WHERE clause. See #filter for argument types.
|
|
770
|
+
#
|
|
771
|
+
# dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
|
|
772
|
+
# dataset.group(:a).having(:a).where(:b) # SELECT * FROM items WHERE b GROUP BY a HAVING a
|
|
773
|
+
def where(*cond, &block)
|
|
774
|
+
_filter(:where, *cond, &block)
|
|
775
|
+
end
|
|
776
|
+
|
|
745
777
|
# Returns a copy of the dataset with the static SQL used. This is useful if you want
|
|
746
778
|
# to keep the same row_proc/transform/graph, but change the SQL used to custom SQL.
|
|
747
|
-
|
|
779
|
+
#
|
|
780
|
+
# dataset.with_sql('SELECT * FROM foo') # SELECT * FROM foo
|
|
781
|
+
def with_sql(sql, *args)
|
|
782
|
+
sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
|
|
748
783
|
clone(:sql=>sql)
|
|
749
784
|
end
|
|
750
785
|
|
|
751
786
|
[:inner, :full_outer, :right_outer, :left_outer].each do |jtype|
|
|
752
787
|
class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end")
|
|
753
788
|
end
|
|
754
|
-
|
|
789
|
+
alias join inner_join
|
|
755
790
|
|
|
756
791
|
protected
|
|
757
792
|
|
|
@@ -764,6 +799,15 @@ module Sequel
|
|
|
764
799
|
|
|
765
800
|
private
|
|
766
801
|
|
|
802
|
+
# Internal filter method so it works on either the having or where clauses.
|
|
803
|
+
def _filter(clause, *cond, &block)
|
|
804
|
+
cond = cond.first if cond.size == 1
|
|
805
|
+
cond = transform_save(cond) if @transform if cond.is_a?(Hash)
|
|
806
|
+
cond = filter_expr(cond, &block)
|
|
807
|
+
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
|
|
808
|
+
clone(clause => cond)
|
|
809
|
+
end
|
|
810
|
+
|
|
767
811
|
# SQL fragment for specifying an alias. expression should already be literalized.
|
|
768
812
|
def as_sql(expression, aliaz)
|
|
769
813
|
"#{expression} AS #{quote_identifier(aliaz)}"
|
|
@@ -772,7 +816,7 @@ module Sequel
|
|
|
772
816
|
# Converts an array of column names into a comma seperated string of
|
|
773
817
|
# column names. If the array is empty, a wildcard (*) is returned.
|
|
774
818
|
def column_list(columns)
|
|
775
|
-
if columns.
|
|
819
|
+
if columns.nil? || columns.empty?
|
|
776
820
|
WILDCARD
|
|
777
821
|
else
|
|
778
822
|
m = columns.map do |i|
|
|
@@ -811,7 +855,7 @@ module Sequel
|
|
|
811
855
|
SQL::BooleanExpression.from_value_pairs(expr)
|
|
812
856
|
end
|
|
813
857
|
when Proc
|
|
814
|
-
filter_expr(expr
|
|
858
|
+
filter_expr(virtual_row_block_call(expr))
|
|
815
859
|
when SQL::NumericExpression, SQL::StringExpression
|
|
816
860
|
raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
|
|
817
861
|
when Symbol, SQL::Expression
|
|
@@ -862,7 +906,7 @@ module Sequel
|
|
|
862
906
|
|
|
863
907
|
# SQL fragment for Array. Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
|
|
864
908
|
def literal_array(v)
|
|
865
|
-
|
|
909
|
+
Sequel.condition_specifier?(v) ? literal_expression(SQL::BooleanExpression.from_value_pairs(v)) : array_sql(v)
|
|
866
910
|
end
|
|
867
911
|
|
|
868
912
|
# SQL fragment for BigDecimal
|
|
@@ -908,7 +952,7 @@ module Sequel
|
|
|
908
952
|
|
|
909
953
|
# SQL fragment for Hash, treated as an expression
|
|
910
954
|
def literal_hash(v)
|
|
911
|
-
literal_expression(v
|
|
955
|
+
literal_expression(SQL::BooleanExpression.from_value_pairs(v))
|
|
912
956
|
end
|
|
913
957
|
|
|
914
958
|
# SQL fragment for Integer
|
|
@@ -926,7 +970,14 @@ module Sequel
|
|
|
926
970
|
"'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
|
|
927
971
|
end
|
|
928
972
|
|
|
929
|
-
#
|
|
973
|
+
# Converts a symbol into a column name. This method supports underscore
|
|
974
|
+
# notation in order to express qualified (two underscores) and aliased
|
|
975
|
+
# (three underscores) columns:
|
|
976
|
+
#
|
|
977
|
+
# dataset.literal(:abc) #=> "abc"
|
|
978
|
+
# dataset.literal(:abc___a) #=> "abc AS a"
|
|
979
|
+
# dataset.literal(:items__abc) #=> "items.abc"
|
|
980
|
+
# dataset.literal(:items__abc___a) #=> "items.abc AS a"
|
|
930
981
|
def literal_symbol(v)
|
|
931
982
|
c_table, column, c_alias = split_symbol(v)
|
|
932
983
|
qc = "#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}"
|
|
@@ -1080,12 +1131,12 @@ module Sequel
|
|
|
1080
1131
|
# SQL fragment specifying a table name.
|
|
1081
1132
|
def table_ref(t)
|
|
1082
1133
|
case t
|
|
1134
|
+
when Symbol
|
|
1135
|
+
literal_symbol(t)
|
|
1083
1136
|
when Dataset
|
|
1084
1137
|
t.to_table_reference
|
|
1085
1138
|
when Hash
|
|
1086
1139
|
t.map{|k, v| as_sql(table_ref(k), v)}.join(COMMA_SEPARATOR)
|
|
1087
|
-
when Symbol
|
|
1088
|
-
symbol_to_column_ref(t)
|
|
1089
1140
|
when String
|
|
1090
1141
|
quote_identifier(t)
|
|
1091
1142
|
else
|