sequel_core 2.2.0 → 3.8.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.
- metadata +30 -101
- data/CHANGELOG +0 -1519
- data/COPYING +0 -19
- data/README +0 -313
- data/Rakefile +0 -158
- data/bin/sequel +0 -117
- data/doc/cheat_sheet.rdoc +0 -225
- data/doc/dataset_filtering.rdoc +0 -182
- data/lib/sequel_core.rb +0 -136
- data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -68
- data/lib/sequel_core/adapters/ado.rb +0 -90
- data/lib/sequel_core/adapters/db2.rb +0 -160
- data/lib/sequel_core/adapters/dbi.rb +0 -127
- data/lib/sequel_core/adapters/informix.rb +0 -89
- data/lib/sequel_core/adapters/jdbc.rb +0 -110
- data/lib/sequel_core/adapters/mysql.rb +0 -486
- data/lib/sequel_core/adapters/odbc.rb +0 -167
- data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
- data/lib/sequel_core/adapters/openbase.rb +0 -76
- data/lib/sequel_core/adapters/oracle.rb +0 -182
- data/lib/sequel_core/adapters/postgres.rb +0 -560
- data/lib/sequel_core/adapters/sqlite.rb +0 -270
- data/lib/sequel_core/connection_pool.rb +0 -194
- data/lib/sequel_core/core_ext.rb +0 -197
- data/lib/sequel_core/core_sql.rb +0 -184
- data/lib/sequel_core/database.rb +0 -462
- data/lib/sequel_core/database/schema.rb +0 -156
- data/lib/sequel_core/dataset.rb +0 -457
- data/lib/sequel_core/dataset/callback.rb +0 -13
- data/lib/sequel_core/dataset/convenience.rb +0 -245
- data/lib/sequel_core/dataset/pagination.rb +0 -96
- data/lib/sequel_core/dataset/query.rb +0 -41
- data/lib/sequel_core/dataset/schema.rb +0 -15
- data/lib/sequel_core/dataset/sql.rb +0 -889
- data/lib/sequel_core/deprecated.rb +0 -26
- data/lib/sequel_core/exceptions.rb +0 -42
- data/lib/sequel_core/migration.rb +0 -187
- data/lib/sequel_core/object_graph.rb +0 -216
- data/lib/sequel_core/pretty_table.rb +0 -71
- data/lib/sequel_core/schema.rb +0 -2
- data/lib/sequel_core/schema/generator.rb +0 -239
- data/lib/sequel_core/schema/sql.rb +0 -326
- data/lib/sequel_core/sql.rb +0 -812
- data/lib/sequel_core/worker.rb +0 -68
- data/spec/adapters/informix_spec.rb +0 -96
- data/spec/adapters/mysql_spec.rb +0 -765
- data/spec/adapters/oracle_spec.rb +0 -222
- data/spec/adapters/postgres_spec.rb +0 -441
- data/spec/adapters/sqlite_spec.rb +0 -413
- data/spec/connection_pool_spec.rb +0 -363
- data/spec/core_ext_spec.rb +0 -156
- data/spec/core_sql_spec.rb +0 -427
- data/spec/database_spec.rb +0 -963
- data/spec/dataset_spec.rb +0 -2933
- data/spec/expression_filters_spec.rb +0 -316
- data/spec/migration_spec.rb +0 -261
- data/spec/object_graph_spec.rb +0 -230
- data/spec/pretty_table_spec.rb +0 -58
- data/spec/rcov.opts +0 -6
- data/spec/schema_generator_spec.rb +0 -122
- data/spec/schema_spec.rb +0 -422
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb +0 -7
- data/spec/spec_config.rb.example +0 -8
- data/spec/spec_helper.rb +0 -55
- data/spec/worker_spec.rb +0 -96
@@ -1,13 +0,0 @@
|
|
1
|
-
module Sequel
|
2
|
-
class Dataset
|
3
|
-
private
|
4
|
-
# This is run inside .all, after all
|
5
|
-
# of the records have been loaded
|
6
|
-
# via .each, but before any block passed
|
7
|
-
# to all is called. It is called with
|
8
|
-
# a single argument, an array of all
|
9
|
-
# returned records.
|
10
|
-
def post_load(all_records)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,245 +0,0 @@
|
|
1
|
-
module Sequel
|
2
|
-
class Dataset
|
3
|
-
COMMA_SEPARATOR = ', '.freeze
|
4
|
-
COUNT_OF_ALL_AS_COUNT = :count['*'.lit].as(:count)
|
5
|
-
|
6
|
-
# Returns the first record matching the conditions.
|
7
|
-
def [](*conditions)
|
8
|
-
first(*conditions)
|
9
|
-
end
|
10
|
-
|
11
|
-
# Update all records matching the conditions
|
12
|
-
# with the values specified.
|
13
|
-
def []=(conditions, values)
|
14
|
-
filter(conditions).update(values)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Returns the average value for the given column.
|
18
|
-
def avg(column)
|
19
|
-
get(:avg[column])
|
20
|
-
end
|
21
|
-
|
22
|
-
# Returns true if no records exists in the dataset
|
23
|
-
def empty?
|
24
|
-
db.dataset.where(exists).get(1) == nil
|
25
|
-
end
|
26
|
-
|
27
|
-
# Returns the first record in the dataset. If a numeric argument is
|
28
|
-
# given, it is interpreted as a limit, and then returns all
|
29
|
-
# matching records up to that limit. If no argument is passed,
|
30
|
-
# it returns the first matching record. If any other type of
|
31
|
-
# argument(s) is passed, it is given to filter and the
|
32
|
-
# first matching record is returned. If a block is given, it is used
|
33
|
-
# to filter the dataset before returning anything.
|
34
|
-
#
|
35
|
-
# Examples:
|
36
|
-
#
|
37
|
-
# ds.first => {:id=>7}
|
38
|
-
# ds.first(2) => [{:id=>6}, {:id=>4}]
|
39
|
-
# ds.order(:id).first(2) => [{:id=>1}, {:id=>2}]
|
40
|
-
# ds.first(:id=>2) => {:id=>2}
|
41
|
-
# ds.first("id = 3") => {:id=>3}
|
42
|
-
# ds.first("id = ?", 4) => {:id=>4}
|
43
|
-
# ds.first{:id > 2} => {:id=>5}
|
44
|
-
# ds.order(:id).first{:id > 2} => {:id=>3}
|
45
|
-
# ds.first{:id > 2} => {:id=>5}
|
46
|
-
# ds.first("id > ?", 4){:id < 6) => {:id=>5}
|
47
|
-
# ds.order(:id).first(2){:id < 2} => [{:id=>1}]
|
48
|
-
def first(*args, &block)
|
49
|
-
ds = block ? filter(&block) : self
|
50
|
-
|
51
|
-
if args.empty?
|
52
|
-
ds.single_record
|
53
|
-
else
|
54
|
-
args = (args.size == 1) ? args.first : args
|
55
|
-
if Integer === args
|
56
|
-
ds.limit(args).all
|
57
|
-
else
|
58
|
-
ds.filter(args).single_record
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Return the column value for the first matching record in the dataset.
|
64
|
-
def get(column)
|
65
|
-
select(column).single_value
|
66
|
-
end
|
67
|
-
|
68
|
-
# Returns a dataset grouped by the given column with count by group.
|
69
|
-
def group_and_count(*columns)
|
70
|
-
group(*columns).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the interval between minimum and maximum values for the given
|
74
|
-
# column.
|
75
|
-
def interval(column)
|
76
|
-
get("(max(#{literal(column)}) - min(#{literal(column)}))".lit)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Reverses the order and then runs first. Note that this
|
80
|
-
# will not necessarily give you the last record in the dataset,
|
81
|
-
# unless you have an unambiguous order. If there is not
|
82
|
-
# currently an order for this dataset, raises an Error.
|
83
|
-
def last(*args, &block)
|
84
|
-
raise(Error, 'No order specified') unless @opts[:order]
|
85
|
-
reverse.first(*args, &block)
|
86
|
-
end
|
87
|
-
|
88
|
-
# Maps column values for each record in the dataset (if a column name is
|
89
|
-
# given), or performs the stock mapping functionality of Enumerable.
|
90
|
-
def map(column_name = nil, &block)
|
91
|
-
if column_name
|
92
|
-
super() {|r| r[column_name]}
|
93
|
-
else
|
94
|
-
super(&block)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Returns the maximum value for the given column.
|
99
|
-
def max(column)
|
100
|
-
get(:max[column])
|
101
|
-
end
|
102
|
-
|
103
|
-
# Returns the minimum value for the given column.
|
104
|
-
def min(column)
|
105
|
-
get(:min[column])
|
106
|
-
end
|
107
|
-
|
108
|
-
# Inserts multiple records into the associated table. This method can be
|
109
|
-
# to efficiently insert a large amounts of records into a table. Inserts
|
110
|
-
# are automatically wrapped in a transaction.
|
111
|
-
#
|
112
|
-
# This method should be called with a columns array and an array of value arrays:
|
113
|
-
#
|
114
|
-
# dataset.multi_insert([:x, :y], [[1, 2], [3, 4]])
|
115
|
-
#
|
116
|
-
# This method can also be called with an array of hashes:
|
117
|
-
#
|
118
|
-
# dataset.multi_insert({:x => 1}, {:x => 2})
|
119
|
-
#
|
120
|
-
# Be aware that all hashes should have the same keys if you use this calling method,
|
121
|
-
# otherwise some columns could be missed or set to null instead of to default
|
122
|
-
# values.
|
123
|
-
#
|
124
|
-
# The method also accepts a :slice or :commit_every option that specifies
|
125
|
-
# the number of records to insert per transaction. This is useful especially
|
126
|
-
# when inserting a large number of records, e.g.:
|
127
|
-
#
|
128
|
-
# # this will commit every 50 records
|
129
|
-
# dataset.multi_insert(lots_of_records, :slice => 50)
|
130
|
-
def multi_insert(*args)
|
131
|
-
if args.empty?
|
132
|
-
return
|
133
|
-
elsif args[0].is_a?(Array) && args[1].is_a?(Array)
|
134
|
-
columns, values, opts = *args
|
135
|
-
elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
|
136
|
-
table = @opts[:from].first
|
137
|
-
columns, dataset = *args
|
138
|
-
sql = "INSERT INTO #{quote_identifier(table)} #{literal(columns)} VALUES #{literal(dataset)}"
|
139
|
-
return @db.transaction {@db.execute sql}
|
140
|
-
else
|
141
|
-
# we assume that an array of hashes is given
|
142
|
-
hashes, opts = *args
|
143
|
-
return if hashes.empty?
|
144
|
-
columns = hashes.first.keys
|
145
|
-
# convert the hashes into arrays
|
146
|
-
values = hashes.map {|h| columns.map {|c| h[c]}}
|
147
|
-
end
|
148
|
-
# make sure there's work to do
|
149
|
-
return if columns.empty? || values.empty?
|
150
|
-
|
151
|
-
slice_size = opts && (opts[:commit_every] || opts[:slice])
|
152
|
-
|
153
|
-
if slice_size
|
154
|
-
values.each_slice(slice_size) do |slice|
|
155
|
-
statements = multi_insert_sql(columns, slice)
|
156
|
-
@db.transaction {statements.each {|st| @db.execute(st)}}
|
157
|
-
end
|
158
|
-
else
|
159
|
-
statements = multi_insert_sql(columns, values)
|
160
|
-
@db.transaction {statements.each {|st| @db.execute(st)}}
|
161
|
-
end
|
162
|
-
end
|
163
|
-
alias_method :import, :multi_insert
|
164
|
-
|
165
|
-
# Pretty prints the records in the dataset as plain-text table.
|
166
|
-
def print(*cols)
|
167
|
-
Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
|
168
|
-
end
|
169
|
-
|
170
|
-
# Returns a Range object made from the minimum and maximum values for the
|
171
|
-
# given column.
|
172
|
-
def range(column)
|
173
|
-
if r = select(:min[column].as(:v1), :max[column].as(:v2)).first
|
174
|
-
(r[:v1]..r[:v2])
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
# Returns the first record in the dataset.
|
179
|
-
def single_record(opts = nil)
|
180
|
-
each((opts||{}).merge(:limit=>1)){|r| return r}
|
181
|
-
nil
|
182
|
-
end
|
183
|
-
|
184
|
-
# Returns the first value of the first record in the dataset.
|
185
|
-
# Returns nil if dataset is empty.
|
186
|
-
def single_value(opts = nil)
|
187
|
-
if r = naked.single_record(opts)
|
188
|
-
r.values.first
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# Returns the sum for the given column.
|
193
|
-
def sum(column)
|
194
|
-
get(:sum[column])
|
195
|
-
end
|
196
|
-
|
197
|
-
# Returns true if the table exists. Will raise an error
|
198
|
-
# if the dataset has fixed SQL or selects from another dataset
|
199
|
-
# or more than one table.
|
200
|
-
def table_exists?
|
201
|
-
if @opts[:sql]
|
202
|
-
raise Sequel::Error, "this dataset has fixed SQL"
|
203
|
-
end
|
204
|
-
|
205
|
-
if @opts[:from].size != 1
|
206
|
-
raise Sequel::Error, "this dataset selects from multiple sources"
|
207
|
-
end
|
208
|
-
|
209
|
-
t = @opts[:from].first
|
210
|
-
if t.is_a?(Dataset)
|
211
|
-
raise Sequel::Error, "this dataset selects from a sub query"
|
212
|
-
end
|
213
|
-
|
214
|
-
@db.table_exists?(t.to_sym)
|
215
|
-
end
|
216
|
-
|
217
|
-
# Returns a string in CSV format containing the dataset records. By
|
218
|
-
# default the CSV representation includes the column titles in the
|
219
|
-
# first line. You can turn that off by passing false as the
|
220
|
-
# include_column_titles argument.
|
221
|
-
#
|
222
|
-
# This does not use a CSV library or handle quoting of values in
|
223
|
-
# any way. If any values in any of the rows could include commas or line
|
224
|
-
# endings, you probably shouldn't use this.
|
225
|
-
def to_csv(include_column_titles = true)
|
226
|
-
n = naked
|
227
|
-
cols = n.columns
|
228
|
-
csv = ''
|
229
|
-
csv << "#{cols.join(COMMA_SEPARATOR)}\r\n" if include_column_titles
|
230
|
-
n.each{|r| csv << "#{cols.collect{|c| r[c]}.join(COMMA_SEPARATOR)}\r\n"}
|
231
|
-
csv
|
232
|
-
end
|
233
|
-
|
234
|
-
# Returns a hash with one column used as key and another used as value.
|
235
|
-
# If rows have duplicate values for the key column, the latter row(s)
|
236
|
-
# will overwrite the value of the previous row(s). If the value_column
|
237
|
-
# is not given or nil, uses the entire hash as the value.
|
238
|
-
def to_hash(key_column, value_column = nil)
|
239
|
-
inject({}) do |m, r|
|
240
|
-
m[r[key_column]] = value_column ? r[value_column] : r
|
241
|
-
m
|
242
|
-
end
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
module Sequel
|
2
|
-
class Dataset
|
3
|
-
# Returns a paginated dataset. The returned dataset is limited to
|
4
|
-
# the page size at the correct offset, and extended with the Pagination
|
5
|
-
# module. If a record count is not provided, does a count of total
|
6
|
-
# number of records for this dataset.
|
7
|
-
def paginate(page_no, page_size, record_count=nil)
|
8
|
-
raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
|
9
|
-
paginated = limit(page_size, (page_no - 1) * page_size)
|
10
|
-
paginated.extend(Pagination)
|
11
|
-
paginated.set_pagination_info(page_no, page_size, record_count || count)
|
12
|
-
end
|
13
|
-
|
14
|
-
# Yields a paginated dataset for each page and returns the receiver. Does
|
15
|
-
# a count to find the total number of records for this dataset.
|
16
|
-
def each_page(page_size, &block)
|
17
|
-
raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
|
18
|
-
record_count = count
|
19
|
-
total_pages = (record_count / page_size.to_f).ceil
|
20
|
-
(1..total_pages).each{|page_no| yield paginate(page_no, page_size, record_count)}
|
21
|
-
self
|
22
|
-
end
|
23
|
-
|
24
|
-
# Holds methods that only relate to paginated datasets. Paginated dataset
|
25
|
-
# have pages starting at 1 (page 1 is offset 0, page 1 is offset page_size).
|
26
|
-
module Pagination
|
27
|
-
# The number of records per page (the final page may have fewer than
|
28
|
-
# this number of records).
|
29
|
-
attr_accessor :page_size
|
30
|
-
|
31
|
-
# The number of pages in the dataset before pagination, of which
|
32
|
-
# this paginated dataset is one.
|
33
|
-
attr_accessor :page_count
|
34
|
-
|
35
|
-
# The current page of the dataset, starting at 1 and not 0.
|
36
|
-
attr_accessor :current_page
|
37
|
-
|
38
|
-
# The total number of records in the dataset before pagination.
|
39
|
-
attr_accessor :pagination_record_count
|
40
|
-
|
41
|
-
# Returns the record range for the current page
|
42
|
-
def current_page_record_range
|
43
|
-
return (0..0) if @current_page > @page_count
|
44
|
-
|
45
|
-
a = 1 + (@current_page - 1) * @page_size
|
46
|
-
b = a + @page_size - 1
|
47
|
-
b = @pagination_record_count if b > @pagination_record_count
|
48
|
-
a..b
|
49
|
-
end
|
50
|
-
|
51
|
-
# Returns the number of records in the current page
|
52
|
-
def current_page_record_count
|
53
|
-
return 0 if @current_page > @page_count
|
54
|
-
|
55
|
-
a = 1 + (@current_page - 1) * @page_size
|
56
|
-
b = a + @page_size - 1
|
57
|
-
b = @pagination_record_count if b > @pagination_record_count
|
58
|
-
b - a + 1
|
59
|
-
end
|
60
|
-
|
61
|
-
# Returns true if the current page is the first page
|
62
|
-
def first_page?
|
63
|
-
@current_page == 1
|
64
|
-
end
|
65
|
-
|
66
|
-
# Returns true if the current page is the last page
|
67
|
-
def last_page?
|
68
|
-
@current_page == @page_count
|
69
|
-
end
|
70
|
-
|
71
|
-
# Returns the next page number or nil if the current page is the last page
|
72
|
-
def next_page
|
73
|
-
current_page < page_count ? (current_page + 1) : nil
|
74
|
-
end
|
75
|
-
|
76
|
-
# Returns the page range
|
77
|
-
def page_range
|
78
|
-
1..page_count
|
79
|
-
end
|
80
|
-
|
81
|
-
# Returns the previous page number or nil if the current page is the first
|
82
|
-
def prev_page
|
83
|
-
current_page > 1 ? (current_page - 1) : nil
|
84
|
-
end
|
85
|
-
|
86
|
-
# Sets the pagination info for this paginated dataset, and returns self.
|
87
|
-
def set_pagination_info(page_no, page_size, record_count)
|
88
|
-
@current_page = page_no
|
89
|
-
@page_size = page_size
|
90
|
-
@pagination_record_count = record_count
|
91
|
-
@page_count = (record_count / page_size.to_f).ceil
|
92
|
-
self
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
module Sequel
|
2
|
-
class Dataset
|
3
|
-
# Translates a query block into a dataset. Query blocks can be useful
|
4
|
-
# when expressing complex SELECT statements, e.g.:
|
5
|
-
#
|
6
|
-
# dataset = DB[:items].query do
|
7
|
-
# select :x, :y, :z
|
8
|
-
# filter((:x > 1) & (:y > 2))
|
9
|
-
# order :z.desc
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# Which is the same as:
|
13
|
-
#
|
14
|
-
# dataset = DB[:items].select(:x, :y, :z).filter((:x > 1) & (:y > 2)).order(:z.desc)
|
15
|
-
#
|
16
|
-
# Note that inside a call to query, you cannot call each, insert, update,
|
17
|
-
# or delete (or any method that calls those), or Sequel will raise an
|
18
|
-
# error.
|
19
|
-
def query(&block)
|
20
|
-
copy = clone({})
|
21
|
-
copy.extend(QueryBlockCopy)
|
22
|
-
copy.instance_eval(&block)
|
23
|
-
clone(copy.opts)
|
24
|
-
end
|
25
|
-
|
26
|
-
# Module used by Dataset#query that has the effect of making all
|
27
|
-
# dataset methods into !-style methods that modify the receiver.
|
28
|
-
module QueryBlockCopy
|
29
|
-
%w'each insert update delete'.each do |meth|
|
30
|
-
define_method(meth){|*args| raise Error, "##{meth} cannot be invoked inside a query block."}
|
31
|
-
end
|
32
|
-
|
33
|
-
# Merge the given options into the receiver's options and return the receiver
|
34
|
-
# instead of cloning the receiver.
|
35
|
-
def clone(opts = nil)
|
36
|
-
@opts.merge!(opts)
|
37
|
-
self
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module Sequel
|
2
|
-
class Dataset
|
3
|
-
# Creates a view in the database with the given named based
|
4
|
-
# on the current dataset.
|
5
|
-
def create_view(name)
|
6
|
-
@db.create_view(name, self)
|
7
|
-
end
|
8
|
-
|
9
|
-
# Creates or replaces a view in the database with the given
|
10
|
-
# named based on the current dataset.
|
11
|
-
def create_or_replace_view(name)
|
12
|
-
@db.create_or_replace_view(name, self)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,889 +0,0 @@
|
|
1
|
-
module Sequel
|
2
|
-
class Dataset
|
3
|
-
AND_SEPARATOR = " AND ".freeze
|
4
|
-
BOOL_FALSE = "'f'".freeze
|
5
|
-
BOOL_TRUE = "'t'".freeze
|
6
|
-
COLUMN_REF_RE1 = /\A([\w ]+)__([\w ]+)___([\w ]+)\z/.freeze
|
7
|
-
COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
|
8
|
-
COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
|
9
|
-
COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit]
|
10
|
-
DATE_FORMAT = "DATE '%Y-%m-%d'".freeze
|
11
|
-
N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
|
12
|
-
NULL = "NULL".freeze
|
13
|
-
QUESTION_MARK = '?'.freeze
|
14
|
-
STOCK_COUNT_OPTS = {:select => ["COUNT(*)".lit], :order => nil}.freeze
|
15
|
-
TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S'".freeze
|
16
|
-
TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
|
17
|
-
WILDCARD = '*'.freeze
|
18
|
-
|
19
|
-
# Adds an further filter to an existing filter using AND. If no filter
|
20
|
-
# exists an error is raised. This method is identical to #filter except
|
21
|
-
# it expects an existing filter.
|
22
|
-
def and(*cond, &block)
|
23
|
-
raise(Error::NoExistingFilter, "No existing filter found.") unless @opts[:having] || @opts[:where]
|
24
|
-
filter(*cond, &block)
|
25
|
-
end
|
26
|
-
|
27
|
-
# SQL fragment for the aliased expression
|
28
|
-
def aliased_expression_sql(ae)
|
29
|
-
"#{literal(ae.expression)} AS #{quote_identifier(ae.aliaz)}"
|
30
|
-
end
|
31
|
-
|
32
|
-
# SQL fragment for specifying given CaseExpression.
|
33
|
-
def case_expression_sql(ce)
|
34
|
-
"(CASE #{ce.conditions.collect{|c,r| "WHEN #{literal(c)} THEN #{literal(r)} "}.join}ELSE #{literal(ce.default)} END)"
|
35
|
-
end
|
36
|
-
|
37
|
-
# SQL fragment for specifying all columns in a given table.
|
38
|
-
def column_all_sql(ca)
|
39
|
-
"#{quote_identifier(ca.table)}.*"
|
40
|
-
end
|
41
|
-
|
42
|
-
# SQL fragment for complex expressions
|
43
|
-
def complex_expression_sql(op, args)
|
44
|
-
case op
|
45
|
-
when *TWO_ARITY_OPERATORS
|
46
|
-
"(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
|
47
|
-
when *N_ARITY_OPERATORS
|
48
|
-
"(#{args.collect{|a| literal(a)}.join(" #{op} ")})"
|
49
|
-
when :NOT
|
50
|
-
"NOT #{literal(args.at(0))}"
|
51
|
-
when :NOOP
|
52
|
-
literal(args.at(0))
|
53
|
-
when :'B~'
|
54
|
-
"~#{literal(args.at(0))}"
|
55
|
-
else
|
56
|
-
raise(Sequel::Error, "invalid operator #{op}")
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# Returns the number of records in the dataset.
|
61
|
-
def count
|
62
|
-
options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count : single_value(STOCK_COUNT_OPTS).to_i
|
63
|
-
end
|
64
|
-
alias_method :size, :count
|
65
|
-
|
66
|
-
# Formats a DELETE statement using the given options and dataset options.
|
67
|
-
#
|
68
|
-
# dataset.filter(:price >= 100).delete_sql #=>
|
69
|
-
# "DELETE FROM items WHERE (price >= 100)"
|
70
|
-
def delete_sql(opts = nil)
|
71
|
-
opts = opts ? @opts.merge(opts) : @opts
|
72
|
-
|
73
|
-
if opts[:group]
|
74
|
-
raise Error::InvalidOperation, "Grouped datasets cannot be deleted from"
|
75
|
-
elsif opts[:from].is_a?(Array) && opts[:from].size > 1
|
76
|
-
raise Error::InvalidOperation, "Joined datasets cannot be deleted from"
|
77
|
-
end
|
78
|
-
|
79
|
-
sql = "DELETE FROM #{source_list(opts[:from])}"
|
80
|
-
|
81
|
-
if where = opts[:where]
|
82
|
-
sql << " WHERE #{literal(where)}"
|
83
|
-
end
|
84
|
-
|
85
|
-
sql
|
86
|
-
end
|
87
|
-
|
88
|
-
# Adds an EXCEPT clause using a second dataset object. If all is true the
|
89
|
-
# clause used is EXCEPT ALL, which may return duplicate rows.
|
90
|
-
#
|
91
|
-
# DB[:items].except(DB[:other_items]).sql
|
92
|
-
# #=> "SELECT * FROM items EXCEPT SELECT * FROM other_items"
|
93
|
-
def except(dataset, all = false)
|
94
|
-
clone(:except => dataset, :except_all => all)
|
95
|
-
end
|
96
|
-
|
97
|
-
# Performs the inverse of Dataset#filter.
|
98
|
-
#
|
99
|
-
# dataset.exclude(:category => 'software').sql #=>
|
100
|
-
# "SELECT * FROM items WHERE (category != 'software')"
|
101
|
-
def exclude(*cond, &block)
|
102
|
-
clause = (@opts[:having] ? :having : :where)
|
103
|
-
cond = cond.first if cond.size == 1
|
104
|
-
cond = cond.sql_or if (Hash === cond) || ((Array === cond) && (cond.all_two_pairs?))
|
105
|
-
cond = filter_expr(cond, &block)
|
106
|
-
cond = SQL::BooleanExpression.invert(cond)
|
107
|
-
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
|
108
|
-
clone(clause => cond)
|
109
|
-
end
|
110
|
-
|
111
|
-
# Returns an EXISTS clause for the dataset.
|
112
|
-
#
|
113
|
-
# DB.select(1).where(DB[:items].exists).sql
|
114
|
-
# #=> "SELECT 1 WHERE EXISTS (SELECT * FROM items)"
|
115
|
-
def exists(opts = nil)
|
116
|
-
"EXISTS (#{select_sql(opts)})"
|
117
|
-
end
|
118
|
-
|
119
|
-
# Returns a copy of the dataset with the given conditions imposed upon it.
|
120
|
-
# If the query has been grouped, then the conditions are imposed in the
|
121
|
-
# HAVING clause. If not, then they are imposed in the WHERE clause. Filter
|
122
|
-
#
|
123
|
-
# filter accepts the following argument types:
|
124
|
-
#
|
125
|
-
# * Hash - list of equality expressions
|
126
|
-
# * Array - depends:
|
127
|
-
# * If first member is a string, assumes the rest of the arguments
|
128
|
-
# are parameters and interpolates them into the string.
|
129
|
-
# * If all members are arrays of length two, treats the same way
|
130
|
-
# as a hash, except it allows for duplicate keys to be
|
131
|
-
# specified.
|
132
|
-
# * String - taken literally
|
133
|
-
# * Symbol - taken as a boolean column argument (e.g. WHERE active)
|
134
|
-
# * Sequel::SQL::BooleanExpression - an existing condition expression,
|
135
|
-
# probably created using the Sequel blockless filter DSL.
|
136
|
-
#
|
137
|
-
# filter also takes a block, which should return one of the above argument
|
138
|
-
# types, and is treated the same way. If both a block and regular argument
|
139
|
-
# are provided, they get ANDed together.
|
140
|
-
#
|
141
|
-
# Examples:
|
142
|
-
#
|
143
|
-
# dataset.filter(:id => 3).sql #=>
|
144
|
-
# "SELECT * FROM items WHERE (id = 3)"
|
145
|
-
# dataset.filter('price < ?', 100).sql #=>
|
146
|
-
# "SELECT * FROM items WHERE price < 100"
|
147
|
-
# dataset.filter([[:id, (1,2,3)], [:id, 0..10]]).sql #=>
|
148
|
-
# "SELECT * FROM items WHERE ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))"
|
149
|
-
# dataset.filter('price < 100').sql #=>
|
150
|
-
# "SELECT * FROM items WHERE price < 100"
|
151
|
-
# dataset.filter(:active).sql #=>
|
152
|
-
# "SELECT * FROM items WHERE :active
|
153
|
-
# dataset.filter(:price < 100).sql #=>
|
154
|
-
# "SELECT * FROM items WHERE (price < 100)"
|
155
|
-
#
|
156
|
-
# Multiple filter calls can be chained for scoping:
|
157
|
-
#
|
158
|
-
# software = dataset.filter(:category => 'software')
|
159
|
-
# software.filter(price < 100).sql #=>
|
160
|
-
# "SELECT * FROM items WHERE ((category = 'software') AND (price < 100))"
|
161
|
-
#
|
162
|
-
# See doc/dataset_filters.rdoc for more examples and details.
|
163
|
-
def filter(*cond, &block)
|
164
|
-
clause = (@opts[:having] ? :having : :where)
|
165
|
-
cond = cond.first if cond.size == 1
|
166
|
-
cond = transform_save(cond) if @transform if cond.is_a?(Hash)
|
167
|
-
cond = filter_expr(cond, &block)
|
168
|
-
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause] && !@opts[clause].blank?
|
169
|
-
clone(clause => cond)
|
170
|
-
end
|
171
|
-
alias_method :where, :filter
|
172
|
-
|
173
|
-
# The first source (primary table) for this dataset. If the dataset doesn't
|
174
|
-
# have a table, raises an error. If the table is aliased, returns the actual
|
175
|
-
# table name, not the alias.
|
176
|
-
def first_source
|
177
|
-
source = @opts[:from]
|
178
|
-
if source.nil? || source.empty?
|
179
|
-
raise Error, 'No source specified for query'
|
180
|
-
end
|
181
|
-
case s = source.first
|
182
|
-
when Hash
|
183
|
-
s.values.first
|
184
|
-
when Symbol
|
185
|
-
sch, table, aliaz = split_symbol(s)
|
186
|
-
aliaz ? aliaz.to_sym : s
|
187
|
-
else
|
188
|
-
s
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# Returns a copy of the dataset with the source changed.
|
193
|
-
def from(*source)
|
194
|
-
clone(:from => source)
|
195
|
-
end
|
196
|
-
|
197
|
-
# Returns a dataset selecting from the current dataset.
|
198
|
-
#
|
199
|
-
# ds = DB[:items].order(:name)
|
200
|
-
# ds.sql #=> "SELECT * FROM items ORDER BY name"
|
201
|
-
# ds.from_self.sql #=> "SELECT * FROM (SELECT * FROM items ORDER BY name)"
|
202
|
-
def from_self
|
203
|
-
fs = {}
|
204
|
-
@opts.keys.each{|k| fs[k] = nil}
|
205
|
-
fs[:from] = [self]
|
206
|
-
clone(fs)
|
207
|
-
end
|
208
|
-
|
209
|
-
# SQL fragment specifying an SQL function call
|
210
|
-
def function_sql(f)
|
211
|
-
args = f.args
|
212
|
-
"#{f.f}#{args.empty? ? '()' : literal(args)}"
|
213
|
-
end
|
214
|
-
|
215
|
-
# Pattern match any of the columns to any of the terms. The terms can be
|
216
|
-
# strings (which use LIKE) or regular expressions (which are only supported
|
217
|
-
# in some databases). See Sequel::SQL::StringExpression.like. Note that the
|
218
|
-
# total number of pattern matches will be cols.length * terms.length,
|
219
|
-
# which could cause performance issues.
|
220
|
-
def grep(cols, terms)
|
221
|
-
filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
|
222
|
-
end
|
223
|
-
|
224
|
-
# Returns a copy of the dataset with the results grouped by the value of
|
225
|
-
# the given columns
|
226
|
-
def group(*columns)
|
227
|
-
clone(:group => columns)
|
228
|
-
end
|
229
|
-
alias_method :group_by, :group
|
230
|
-
|
231
|
-
# Returns a copy of the dataset with the having conditions changed. Raises
|
232
|
-
# an error if the dataset has not been grouped. See also #filter.
|
233
|
-
def having(*cond, &block)
|
234
|
-
raise(Error::InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
|
235
|
-
clone(:having=>{}).filter(*cond, &block)
|
236
|
-
end
|
237
|
-
|
238
|
-
# Inserts multiple values. If a block is given it is invoked for each
|
239
|
-
# item in the given array before inserting it. See #multi_insert as
|
240
|
-
# a possible faster version that inserts multiple records in one
|
241
|
-
# SQL statement.
|
242
|
-
def insert_multiple(array, &block)
|
243
|
-
if block
|
244
|
-
array.each {|i| insert(block[i])}
|
245
|
-
else
|
246
|
-
array.each {|i| insert(i)}
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
# Formats an INSERT statement using the given values. If a hash is given,
|
251
|
-
# the resulting statement includes column names. If no values are given,
|
252
|
-
# the resulting statement includes a DEFAULT VALUES clause.
|
253
|
-
#
|
254
|
-
# dataset.insert_sql() #=> 'INSERT INTO items DEFAULT VALUES'
|
255
|
-
# dataset.insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
256
|
-
# dataset.insert_sql(:a => 1, :b => 2) #=>
|
257
|
-
# 'INSERT INTO items (a, b) VALUES (1, 2)'
|
258
|
-
def insert_sql(*values)
|
259
|
-
if values.empty?
|
260
|
-
insert_default_values_sql
|
261
|
-
else
|
262
|
-
values = values[0] if values.size == 1
|
263
|
-
|
264
|
-
# if hash or array with keys we need to transform the values
|
265
|
-
if @transform && (values.is_a?(Hash) || (values.is_a?(Array) && values.keys))
|
266
|
-
values = transform_save(values)
|
267
|
-
end
|
268
|
-
from = source_list(@opts[:from])
|
269
|
-
|
270
|
-
case values
|
271
|
-
when Array
|
272
|
-
if values.empty?
|
273
|
-
insert_default_values_sql
|
274
|
-
else
|
275
|
-
"INSERT INTO #{from} VALUES #{literal(values)}"
|
276
|
-
end
|
277
|
-
when Hash
|
278
|
-
if values.empty?
|
279
|
-
insert_default_values_sql
|
280
|
-
else
|
281
|
-
fl, vl = [], []
|
282
|
-
values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
|
283
|
-
"INSERT INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
|
284
|
-
end
|
285
|
-
when Dataset
|
286
|
-
"INSERT INTO #{from} #{literal(values)}"
|
287
|
-
else
|
288
|
-
if values.respond_to?(:values)
|
289
|
-
insert_sql(values.values)
|
290
|
-
else
|
291
|
-
"INSERT INTO #{from} VALUES (#{literal(values)})"
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
# Adds an INTERSECT clause using a second dataset object. If all is true
|
298
|
-
# the clause used is INTERSECT ALL, which may return duplicate rows.
|
299
|
-
#
|
300
|
-
# DB[:items].intersect(DB[:other_items]).sql
|
301
|
-
# #=> "SELECT * FROM items INTERSECT SELECT * FROM other_items"
|
302
|
-
def intersect(dataset, all = false)
|
303
|
-
clone(:intersect => dataset, :intersect_all => all)
|
304
|
-
end
|
305
|
-
|
306
|
-
# Inverts the current filter
|
307
|
-
#
|
308
|
-
# dataset.filter(:category => 'software').invert.sql #=>
|
309
|
-
# "SELECT * FROM items WHERE (category != 'software')"
|
310
|
-
def invert
|
311
|
-
having, where = @opts[:having], @opts[:where]
|
312
|
-
raise(Error, "No current filter") unless having || where
|
313
|
-
o = {}
|
314
|
-
o[:having] = SQL::BooleanExpression.invert(having) if having
|
315
|
-
o[:where] = SQL::BooleanExpression.invert(where) if where
|
316
|
-
clone(o)
|
317
|
-
end
|
318
|
-
|
319
|
-
# SQL fragment specifying an Irregular (cast/extract) SQL function call
|
320
|
-
def irregular_function_sql(f)
|
321
|
-
"#{f.f}(#{literal(f.arg1)} #{f.joiner} #{literal(f.arg2)})"
|
322
|
-
end
|
323
|
-
|
324
|
-
# SQL fragment specifying a JOIN clause without ON or USING.
|
325
|
-
def join_clause_sql(jc)
|
326
|
-
table = jc.table
|
327
|
-
table_alias = jc.table_alias
|
328
|
-
table_alias = nil if table == table_alias
|
329
|
-
" #{join_type_sql(jc.join_type)} #{table_ref(table)}" \
|
330
|
-
"#{" AS #{quote_identifier(jc.table_alias)}" if table_alias}"
|
331
|
-
end
|
332
|
-
|
333
|
-
# SQL fragment specifying a JOIN clause with ON.
|
334
|
-
def join_on_clause_sql(jc)
|
335
|
-
"#{join_clause_sql(jc)} ON #{literal(filter_expr(jc.on))}"
|
336
|
-
end
|
337
|
-
|
338
|
-
# SQL fragment specifying a JOIN clause with USING.
|
339
|
-
def join_using_clause_sql(jc)
|
340
|
-
"#{join_clause_sql(jc)} USING (#{column_list(jc.using)})"
|
341
|
-
end
|
342
|
-
|
343
|
-
# Returns a joined dataset. Uses the following arguments:
|
344
|
-
#
|
345
|
-
# * type - The type of join to do (:inner, :left_outer, :right_outer, :full)
|
346
|
-
# * table - Depends on type:
|
347
|
-
# * Dataset - a subselect is performed with an alias of tN for some value of N
|
348
|
-
# * Model (or anything responding to :table_name) - table.table_name
|
349
|
-
# * String, Symbol: table
|
350
|
-
# * expr - specifies conditions, depends on type:
|
351
|
-
# * Hash, Array with all two pairs - Assumes key (1st arg) is column of joined table (unless already
|
352
|
-
# qualified), and value (2nd arg) is column of the last joined or primary table.
|
353
|
-
# To specify multiple conditions on a single joined table column, you must use an array.
|
354
|
-
# Uses a JOIN with an ON clause.
|
355
|
-
# * Array - If all members of the array are symbols, considers them as columns and
|
356
|
-
# uses a JOIN with a USING clause. Most databases will remove duplicate columns from
|
357
|
-
# the result set if this is used.
|
358
|
-
# * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
|
359
|
-
# or CROSS join. If a block is given, uses a ON clause based on the block, see below.
|
360
|
-
# * Everything else - pretty much the same as a using the argument in a call to filter,
|
361
|
-
# so strings are considered literal, symbols specify boolean columns, and blockless
|
362
|
-
# filter expressions can be used. Uses a JOIN with an ON clause.
|
363
|
-
# * table_alias - the name of the table's alias when joining, necessary for joining
|
364
|
-
# to the same table more than once. No alias is used by default.
|
365
|
-
# * block - The block argument should only be given if a JOIN with an ON clause is used,
|
366
|
-
# in which case it yields the table alias/name for the table currently being joined,
|
367
|
-
# the table alias/name for the last joined (or first table), and an array of previous
|
368
|
-
# SQL::JoinClause.
|
369
|
-
def join_table(type, table, expr=nil, table_alias=nil, &block)
|
370
|
-
if Dataset === table
|
371
|
-
if table_alias.nil?
|
372
|
-
table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
|
373
|
-
table_alias = "t#{table_alias_num}"
|
374
|
-
end
|
375
|
-
table_name = table_alias
|
376
|
-
else
|
377
|
-
table = table.table_name if table.respond_to?(:table_name)
|
378
|
-
table_name = table_alias || table
|
379
|
-
end
|
380
|
-
|
381
|
-
join = if expr.nil? and !block_given?
|
382
|
-
SQL::JoinClause.new(type, table, table_alias)
|
383
|
-
elsif Array === expr and !expr.empty? and expr.all?{|x| Symbol === x}
|
384
|
-
raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
|
385
|
-
SQL::JoinUsingClause.new(expr, type, table, table_alias)
|
386
|
-
else
|
387
|
-
last_alias = @opts[:last_joined_table] || first_source
|
388
|
-
if Hash === expr or (Array === expr and expr.all_two_pairs?)
|
389
|
-
expr = expr.collect do |k, v|
|
390
|
-
k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
|
391
|
-
v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
|
392
|
-
[k,v]
|
393
|
-
end
|
394
|
-
end
|
395
|
-
if block_given?
|
396
|
-
expr2 = yield(table_name, last_alias, @opts[:join] || [])
|
397
|
-
expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
|
398
|
-
end
|
399
|
-
SQL::JoinOnClause.new(expr, type, table, table_alias)
|
400
|
-
end
|
401
|
-
|
402
|
-
opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
|
403
|
-
opts[:num_dataset_sources] = table_alias_num if table_alias_num
|
404
|
-
clone(opts)
|
405
|
-
end
|
406
|
-
|
407
|
-
# If given an integer, the dataset will contain only the first l results.
|
408
|
-
# If given a range, it will contain only those at offsets within that
|
409
|
-
# range. If a second argument is given, it is used as an offset.
|
410
|
-
def limit(l, o = nil)
|
411
|
-
return from_self.limit(l, o) if @opts[:sql]
|
412
|
-
|
413
|
-
if Range === l
|
414
|
-
o = l.first
|
415
|
-
l = l.interval + 1
|
416
|
-
end
|
417
|
-
l = l.to_i
|
418
|
-
raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
|
419
|
-
opts = {:limit => l}
|
420
|
-
if o
|
421
|
-
o = o.to_i
|
422
|
-
raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
|
423
|
-
opts[:offset] = o
|
424
|
-
end
|
425
|
-
clone(opts)
|
426
|
-
end
|
427
|
-
|
428
|
-
# Returns a literal representation of a value to be used as part
|
429
|
-
# of an SQL expression.
|
430
|
-
#
|
431
|
-
# dataset.literal("abc'def\\") #=> "'abc''def\\\\'"
|
432
|
-
# dataset.literal(:items__id) #=> "items.id"
|
433
|
-
# dataset.literal([1, 2, 3]) => "(1, 2, 3)"
|
434
|
-
# dataset.literal(DB[:items]) => "(SELECT * FROM items)"
|
435
|
-
# dataset.literal(:x + 1 > :y) => "((x + 1) > y)"
|
436
|
-
#
|
437
|
-
# If an unsupported object is given, an exception is raised.
|
438
|
-
def literal(v)
|
439
|
-
case v
|
440
|
-
when LiteralString
|
441
|
-
v
|
442
|
-
when String
|
443
|
-
"'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
|
444
|
-
when Integer, Float
|
445
|
-
v.to_s
|
446
|
-
when BigDecimal
|
447
|
-
v.to_s("F")
|
448
|
-
when NilClass
|
449
|
-
NULL
|
450
|
-
when TrueClass
|
451
|
-
BOOL_TRUE
|
452
|
-
when FalseClass
|
453
|
-
BOOL_FALSE
|
454
|
-
when Symbol
|
455
|
-
symbol_to_column_ref(v)
|
456
|
-
when ::Sequel::SQL::Expression
|
457
|
-
v.to_s(self)
|
458
|
-
when Array
|
459
|
-
v.all_two_pairs? ? literal(v.sql_expr) : (v.empty? ? '(NULL)' : "(#{v.collect{|i| literal(i)}.join(COMMA_SEPARATOR)})")
|
460
|
-
when Hash
|
461
|
-
literal(v.sql_expr)
|
462
|
-
when Time, DateTime
|
463
|
-
v.strftime(TIMESTAMP_FORMAT)
|
464
|
-
when Date
|
465
|
-
v.strftime(DATE_FORMAT)
|
466
|
-
when Dataset
|
467
|
-
"(#{v.sql})"
|
468
|
-
else
|
469
|
-
raise Error, "can't express #{v.inspect} as a SQL literal"
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
# Returns an array of insert statements for inserting multiple records.
|
474
|
-
# This method is used by #multi_insert to format insert statements and
|
475
|
-
# expects a keys array and and an array of value arrays.
|
476
|
-
#
|
477
|
-
# This method should be overridden by descendants if the support
|
478
|
-
# inserting multiple records in a single SQL statement.
|
479
|
-
def multi_insert_sql(columns, values)
|
480
|
-
table = quote_identifier(@opts[:from].first)
|
481
|
-
columns = literal(columns)
|
482
|
-
values.map do |r|
|
483
|
-
"INSERT INTO #{table} #{columns} VALUES #{literal(r)}"
|
484
|
-
end
|
485
|
-
end
|
486
|
-
|
487
|
-
# Adds an alternate filter to an existing filter using OR. If no filter
|
488
|
-
# exists an error is raised.
|
489
|
-
def or(*cond, &block)
|
490
|
-
clause = (@opts[:having] ? :having : :where)
|
491
|
-
cond = cond.first if cond.size == 1
|
492
|
-
if @opts[clause]
|
493
|
-
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
|
494
|
-
else
|
495
|
-
raise Error::NoExistingFilter, "No existing filter found."
|
496
|
-
end
|
497
|
-
end
|
498
|
-
|
499
|
-
# Returns a copy of the dataset with the order changed. If a nil is given
|
500
|
-
# the returned dataset has no order. This can accept multiple arguments
|
501
|
-
# of varying kinds, and even SQL functions.
|
502
|
-
#
|
503
|
-
# ds.order(:name).sql #=> 'SELECT * FROM items ORDER BY name'
|
504
|
-
# ds.order(:a, :b).sql #=> 'SELECT * FROM items ORDER BY a, b'
|
505
|
-
# ds.order('a + b'.lit).sql #=> 'SELECT * FROM items ORDER BY a + b'
|
506
|
-
# ds.order(:a + :b).sql #=> 'SELECT * FROM items ORDER BY (a + b)'
|
507
|
-
# ds.order(:name.desc).sql #=> 'SELECT * FROM items ORDER BY name DESC'
|
508
|
-
# ds.order(:name.asc).sql #=> 'SELECT * FROM items ORDER BY name ASC'
|
509
|
-
# ds.order(:arr|1).sql #=> 'SELECT * FROM items ORDER BY arr[1]'
|
510
|
-
# ds.order(nil).sql #=> 'SELECT * FROM items'
|
511
|
-
def order(*order)
|
512
|
-
clone(:order => (order.compact.empty?) ? nil : order)
|
513
|
-
end
|
514
|
-
alias_method :order_by, :order
|
515
|
-
|
516
|
-
# Returns a copy of the dataset with the order columns added
|
517
|
-
# to the existing order.
|
518
|
-
def order_more(*order)
|
519
|
-
order(*((@opts[:order] || []) + order))
|
520
|
-
end
|
521
|
-
|
522
|
-
# SQL fragment for the ordered expression, used in the ORDER BY
|
523
|
-
# clause.
|
524
|
-
def ordered_expression_sql(oe)
|
525
|
-
"#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
|
526
|
-
end
|
527
|
-
|
528
|
-
# SQL fragment for the qualifed identifier, specifying
|
529
|
-
# a table and a column (or schema and table).
|
530
|
-
def qualified_identifier_sql(qcr)
|
531
|
-
[qcr.table, qcr.column].map{|x| x.is_one_of?(SQL::QualifiedIdentifier, SQL::Identifier) ? literal(x) : quote_identifier(x)}.join('.')
|
532
|
-
end
|
533
|
-
|
534
|
-
# Adds quoting to identifiers (columns and tables). If identifiers are not
|
535
|
-
# being quoted, returns name as a string. If identifiers are being quoted
|
536
|
-
# quote the name with quoted_identifier.
|
537
|
-
def quote_identifier(name)
|
538
|
-
quote_identifiers? ? quoted_identifier(name) : name.to_s
|
539
|
-
end
|
540
|
-
alias_method :quote_column_ref, :quote_identifier
|
541
|
-
|
542
|
-
# This method quotes the given name with the SQL standard double quote.
|
543
|
-
# It uppercases the name given to conform with the SQL standard. This
|
544
|
-
# should be overridden by subclasses to provide quoting not matching the
|
545
|
-
# SQL standard, such as backtick (used by MySQL and SQLite), or where
|
546
|
-
# lowercase is the default for unquoted identifiers (PostgreSQL).
|
547
|
-
#
|
548
|
-
# If you are using a database such as Oracle that defaults to uppercase
|
549
|
-
# but you are using lower case identifiers, you should override this
|
550
|
-
# method to not upcase the name for those identifiers.
|
551
|
-
def quoted_identifier(name)
|
552
|
-
"\"#{name.to_s.upcase}\""
|
553
|
-
end
|
554
|
-
|
555
|
-
# Returns a copy of the dataset with the order reversed. If no order is
|
556
|
-
# given, the existing order is inverted.
|
557
|
-
def reverse_order(*order)
|
558
|
-
order(*invert_order(order.empty? ? @opts[:order] : order))
|
559
|
-
end
|
560
|
-
alias_method :reverse, :reverse_order
|
561
|
-
|
562
|
-
# Returns a copy of the dataset with the columns selected changed
|
563
|
-
# to the given columns.
|
564
|
-
def select(*columns)
|
565
|
-
clone(:select => columns)
|
566
|
-
end
|
567
|
-
|
568
|
-
# Returns a copy of the dataset selecting the wildcard.
|
569
|
-
def select_all
|
570
|
-
clone(:select => nil)
|
571
|
-
end
|
572
|
-
|
573
|
-
# Returns a copy of the dataset with the given columns added
|
574
|
-
# to the existing selected columns.
|
575
|
-
def select_more(*columns)
|
576
|
-
select(*((@opts[:select] || []) + columns))
|
577
|
-
end
|
578
|
-
|
579
|
-
# Formats a SELECT statement using the given options and the dataset
|
580
|
-
# options.
|
581
|
-
def select_sql(opts = nil)
|
582
|
-
opts = opts ? @opts.merge(opts) : @opts
|
583
|
-
|
584
|
-
if sql = opts[:sql]
|
585
|
-
return sql
|
586
|
-
end
|
587
|
-
|
588
|
-
columns = opts[:select]
|
589
|
-
select_columns = columns ? column_list(columns) : WILDCARD
|
590
|
-
|
591
|
-
if distinct = opts[:distinct]
|
592
|
-
distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{expression_list(distinct)})"
|
593
|
-
sql = "SELECT #{distinct_clause} #{select_columns}"
|
594
|
-
else
|
595
|
-
sql = "SELECT #{select_columns}"
|
596
|
-
end
|
597
|
-
|
598
|
-
if opts[:from]
|
599
|
-
sql << " FROM #{source_list(opts[:from])}"
|
600
|
-
end
|
601
|
-
|
602
|
-
if join = opts[:join]
|
603
|
-
join.each{|j| sql << literal(j)}
|
604
|
-
end
|
605
|
-
|
606
|
-
if where = opts[:where]
|
607
|
-
sql << " WHERE #{literal(where)}"
|
608
|
-
end
|
609
|
-
|
610
|
-
if group = opts[:group]
|
611
|
-
sql << " GROUP BY #{expression_list(group)}"
|
612
|
-
end
|
613
|
-
|
614
|
-
if having = opts[:having]
|
615
|
-
sql << " HAVING #{literal(having)}"
|
616
|
-
end
|
617
|
-
|
618
|
-
if order = opts[:order]
|
619
|
-
sql << " ORDER BY #{expression_list(order)}"
|
620
|
-
end
|
621
|
-
|
622
|
-
if limit = opts[:limit]
|
623
|
-
sql << " LIMIT #{limit}"
|
624
|
-
if offset = opts[:offset]
|
625
|
-
sql << " OFFSET #{offset}"
|
626
|
-
end
|
627
|
-
end
|
628
|
-
|
629
|
-
if union = opts[:union]
|
630
|
-
sql << (opts[:union_all] ? \
|
631
|
-
" UNION ALL #{union.sql}" : " UNION #{union.sql}")
|
632
|
-
elsif intersect = opts[:intersect]
|
633
|
-
sql << (opts[:intersect_all] ? \
|
634
|
-
" INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
|
635
|
-
elsif except = opts[:except]
|
636
|
-
sql << (opts[:except_all] ? \
|
637
|
-
" EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
|
638
|
-
end
|
639
|
-
|
640
|
-
sql
|
641
|
-
end
|
642
|
-
alias_method :sql, :select_sql
|
643
|
-
|
644
|
-
# SQL fragment for specifying subscripts (SQL arrays)
|
645
|
-
def subscript_sql(s)
|
646
|
-
"#{s.f}[#{s.sub.join(COMMA_SEPARATOR)}]"
|
647
|
-
end
|
648
|
-
|
649
|
-
# Converts a symbol into a column name. This method supports underscore
|
650
|
-
# notation in order to express qualified (two underscores) and aliased
|
651
|
-
# (three underscores) columns:
|
652
|
-
#
|
653
|
-
# ds = DB[:items]
|
654
|
-
# :abc.to_column_ref(ds) #=> "abc"
|
655
|
-
# :abc___a.to_column_ref(ds) #=> "abc AS a"
|
656
|
-
# :items__abc.to_column_ref(ds) #=> "items.abc"
|
657
|
-
# :items__abc___a.to_column_ref(ds) #=> "items.abc AS a"
|
658
|
-
#
|
659
|
-
def symbol_to_column_ref(sym)
|
660
|
-
c_table, column, c_alias = split_symbol(sym)
|
661
|
-
"#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}#{" AS #{quote_identifier(c_alias)}" if c_alias}"
|
662
|
-
end
|
663
|
-
|
664
|
-
# Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
|
665
|
-
def unfiltered
|
666
|
-
clone(:where => nil, :having => nil)
|
667
|
-
end
|
668
|
-
|
669
|
-
# Adds a UNION clause using a second dataset object. If all is true the
|
670
|
-
# clause used is UNION ALL, which may return duplicate rows.
|
671
|
-
#
|
672
|
-
# DB[:items].union(DB[:other_items]).sql
|
673
|
-
# #=> "SELECT * FROM items UNION SELECT * FROM other_items"
|
674
|
-
def union(dataset, all = false)
|
675
|
-
clone(:union => dataset, :union_all => all)
|
676
|
-
end
|
677
|
-
|
678
|
-
# Returns a copy of the dataset with the distinct option.
|
679
|
-
def uniq(*args)
|
680
|
-
clone(:distinct => args)
|
681
|
-
end
|
682
|
-
alias_method :distinct, :uniq
|
683
|
-
|
684
|
-
# Returns a copy of the dataset with no order.
|
685
|
-
def unordered
|
686
|
-
order(nil)
|
687
|
-
end
|
688
|
-
|
689
|
-
# Formats an UPDATE statement using the given values.
|
690
|
-
#
|
691
|
-
# dataset.update_sql(:price => 100, :category => 'software') #=>
|
692
|
-
# "UPDATE items SET price = 100, category = 'software'"
|
693
|
-
#
|
694
|
-
# Accepts a block, but such usage is discouraged.
|
695
|
-
#
|
696
|
-
# Raises an error if the dataset is grouped or includes more
|
697
|
-
# than one table.
|
698
|
-
def update_sql(values = {}, opts = nil)
|
699
|
-
opts = opts ? @opts.merge(opts) : @opts
|
700
|
-
|
701
|
-
if opts[:group]
|
702
|
-
raise Error::InvalidOperation, "A grouped dataset cannot be updated"
|
703
|
-
elsif (opts[:from].size > 1) or opts[:join]
|
704
|
-
raise Error::InvalidOperation, "A joined dataset cannot be updated"
|
705
|
-
end
|
706
|
-
|
707
|
-
sql = "UPDATE #{source_list(@opts[:from])} SET "
|
708
|
-
set = if values.is_a?(Hash)
|
709
|
-
# get values from hash
|
710
|
-
values = transform_save(values) if @transform
|
711
|
-
values.map do |k, v|
|
712
|
-
# convert string key into symbol
|
713
|
-
k = k.to_sym if String === k
|
714
|
-
"#{literal(k)} = #{literal(v)}"
|
715
|
-
end.join(COMMA_SEPARATOR)
|
716
|
-
else
|
717
|
-
# copy values verbatim
|
718
|
-
values
|
719
|
-
end
|
720
|
-
sql << set
|
721
|
-
if where = opts[:where]
|
722
|
-
sql << " WHERE #{literal(where)}"
|
723
|
-
end
|
724
|
-
|
725
|
-
sql
|
726
|
-
end
|
727
|
-
|
728
|
-
[:inner, :full_outer, :right_outer, :left_outer].each do |jtype|
|
729
|
-
class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end")
|
730
|
-
end
|
731
|
-
alias_method :join, :inner_join
|
732
|
-
|
733
|
-
protected
|
734
|
-
|
735
|
-
# Returns a table reference for use in the FROM clause. Returns an SQL subquery
|
736
|
-
# frgament with an optional table alias.
|
737
|
-
def to_table_reference(table_alias=nil)
|
738
|
-
"(#{sql})#{" #{quote_identifier(table_alias)}" if table_alias}"
|
739
|
-
end
|
740
|
-
|
741
|
-
private
|
742
|
-
|
743
|
-
# Converts an array of column names into a comma seperated string of
|
744
|
-
# column names. If the array is empty, a wildcard (*) is returned.
|
745
|
-
def column_list(columns)
|
746
|
-
if columns.empty?
|
747
|
-
WILDCARD
|
748
|
-
else
|
749
|
-
m = columns.map do |i|
|
750
|
-
i.is_a?(Hash) ? i.map{|k, v| "#{literal(k)} AS #{quote_identifier(v)}"} : literal(i)
|
751
|
-
end
|
752
|
-
m.join(COMMA_SEPARATOR)
|
753
|
-
end
|
754
|
-
end
|
755
|
-
|
756
|
-
# Converts an array of expressions into a comma separated string of
|
757
|
-
# expressions.
|
758
|
-
def expression_list(columns)
|
759
|
-
columns.map{|i| literal(i)}.join(COMMA_SEPARATOR)
|
760
|
-
end
|
761
|
-
|
762
|
-
# SQL fragment based on the expr type. See #filter.
|
763
|
-
def filter_expr(expr = nil, &block)
|
764
|
-
expr = nil if expr == []
|
765
|
-
if expr && block
|
766
|
-
return SQL::BooleanExpression.new(:AND, filter_expr(expr), filter_expr(block))
|
767
|
-
elsif block
|
768
|
-
expr = block
|
769
|
-
end
|
770
|
-
case expr
|
771
|
-
when Hash
|
772
|
-
SQL::BooleanExpression.from_value_pairs(expr)
|
773
|
-
when Array
|
774
|
-
if String === expr[0]
|
775
|
-
filter_expr(expr.shift.gsub(QUESTION_MARK){literal(expr.shift)}.lit)
|
776
|
-
else
|
777
|
-
SQL::BooleanExpression.from_value_pairs(expr)
|
778
|
-
end
|
779
|
-
when Proc
|
780
|
-
filter_expr(expr.call(SQL::VirtualRow.new))
|
781
|
-
when SQL::NumericExpression, SQL::StringExpression
|
782
|
-
raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
|
783
|
-
when Symbol, SQL::Expression
|
784
|
-
expr
|
785
|
-
when TrueClass, FalseClass
|
786
|
-
SQL::BooleanExpression.new(:NOOP, expr)
|
787
|
-
when String
|
788
|
-
"(#{expr})".lit
|
789
|
-
else
|
790
|
-
raise(Error, 'Invalid filter argument')
|
791
|
-
end
|
792
|
-
end
|
793
|
-
|
794
|
-
# SQL statement for formatting an insert statement with default values
|
795
|
-
def insert_default_values_sql
|
796
|
-
"INSERT INTO #{source_list(@opts[:from])} DEFAULT VALUES"
|
797
|
-
end
|
798
|
-
|
799
|
-
# Inverts the given order by breaking it into a list of column references
|
800
|
-
# and inverting them.
|
801
|
-
#
|
802
|
-
# dataset.invert_order([:id.desc]]) #=> [:id]
|
803
|
-
# dataset.invert_order(:category, :price.desc]) #=>
|
804
|
-
# [:category.desc, :price]
|
805
|
-
def invert_order(order)
|
806
|
-
return nil unless order
|
807
|
-
new_order = []
|
808
|
-
order.map do |f|
|
809
|
-
case f
|
810
|
-
when SQL::OrderedExpression
|
811
|
-
SQL::OrderedExpression.new(f.expression, !f.descending)
|
812
|
-
else
|
813
|
-
SQL::OrderedExpression.new(f)
|
814
|
-
end
|
815
|
-
end
|
816
|
-
end
|
817
|
-
|
818
|
-
# SQL fragment specifying a JOIN type, converts underscores to
|
819
|
-
# spaces and upcases.
|
820
|
-
def join_type_sql(join_type)
|
821
|
-
"#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
|
822
|
-
end
|
823
|
-
|
824
|
-
# Returns a qualified column name (including a table name) if the column
|
825
|
-
# name isn't already qualified.
|
826
|
-
def qualified_column_name(column, table)
|
827
|
-
if Symbol === column
|
828
|
-
c_table, column, c_alias = split_symbol(column)
|
829
|
-
schema, table, t_alias = split_symbol(table) if Symbol === table
|
830
|
-
c_table ||= t_alias || table
|
831
|
-
::Sequel::SQL::QualifiedIdentifier.new(c_table, column)
|
832
|
-
else
|
833
|
-
column
|
834
|
-
end
|
835
|
-
end
|
836
|
-
|
837
|
-
# Converts an array of source names into into a comma separated list.
|
838
|
-
def source_list(source)
|
839
|
-
if source.nil? || source.empty?
|
840
|
-
raise Error, 'No source specified for query'
|
841
|
-
end
|
842
|
-
auto_alias_count = @opts[:num_dataset_sources] || 0
|
843
|
-
m = source.map do |s|
|
844
|
-
case s
|
845
|
-
when Dataset
|
846
|
-
auto_alias_count += 1
|
847
|
-
s.to_table_reference("t#{auto_alias_count}")
|
848
|
-
else
|
849
|
-
table_ref(s)
|
850
|
-
end
|
851
|
-
end
|
852
|
-
m.join(COMMA_SEPARATOR)
|
853
|
-
end
|
854
|
-
|
855
|
-
# Splits the symbol into three parts. Each part will
|
856
|
-
# either be a string or nil.
|
857
|
-
#
|
858
|
-
# For columns, these parts are the table, column, and alias.
|
859
|
-
# For tables, these parts are the schema, table, and alias.
|
860
|
-
def split_symbol(sym)
|
861
|
-
s = sym.to_s
|
862
|
-
if m = COLUMN_REF_RE1.match(s)
|
863
|
-
m[1..3]
|
864
|
-
elsif m = COLUMN_REF_RE2.match(s)
|
865
|
-
[nil, m[1], m[2]]
|
866
|
-
elsif m = COLUMN_REF_RE3.match(s)
|
867
|
-
[m[1], m[2], nil]
|
868
|
-
else
|
869
|
-
[nil, s, nil]
|
870
|
-
end
|
871
|
-
end
|
872
|
-
|
873
|
-
# SQL fragement specifying a table name.
|
874
|
-
def table_ref(t)
|
875
|
-
case t
|
876
|
-
when Dataset
|
877
|
-
t.to_table_reference
|
878
|
-
when Hash
|
879
|
-
t.map {|k, v| "#{table_ref(k)} #{table_ref(v)}"}.join(COMMA_SEPARATOR)
|
880
|
-
when Symbol
|
881
|
-
symbol_to_column_ref(t)
|
882
|
-
when String
|
883
|
-
quote_identifier(t)
|
884
|
-
else
|
885
|
-
literal(t)
|
886
|
-
end
|
887
|
-
end
|
888
|
-
end
|
889
|
-
end
|