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.
Files changed (66) hide show
  1. metadata +30 -101
  2. data/CHANGELOG +0 -1519
  3. data/COPYING +0 -19
  4. data/README +0 -313
  5. data/Rakefile +0 -158
  6. data/bin/sequel +0 -117
  7. data/doc/cheat_sheet.rdoc +0 -225
  8. data/doc/dataset_filtering.rdoc +0 -182
  9. data/lib/sequel_core.rb +0 -136
  10. data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -68
  11. data/lib/sequel_core/adapters/ado.rb +0 -90
  12. data/lib/sequel_core/adapters/db2.rb +0 -160
  13. data/lib/sequel_core/adapters/dbi.rb +0 -127
  14. data/lib/sequel_core/adapters/informix.rb +0 -89
  15. data/lib/sequel_core/adapters/jdbc.rb +0 -110
  16. data/lib/sequel_core/adapters/mysql.rb +0 -486
  17. data/lib/sequel_core/adapters/odbc.rb +0 -167
  18. data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
  19. data/lib/sequel_core/adapters/openbase.rb +0 -76
  20. data/lib/sequel_core/adapters/oracle.rb +0 -182
  21. data/lib/sequel_core/adapters/postgres.rb +0 -560
  22. data/lib/sequel_core/adapters/sqlite.rb +0 -270
  23. data/lib/sequel_core/connection_pool.rb +0 -194
  24. data/lib/sequel_core/core_ext.rb +0 -197
  25. data/lib/sequel_core/core_sql.rb +0 -184
  26. data/lib/sequel_core/database.rb +0 -462
  27. data/lib/sequel_core/database/schema.rb +0 -156
  28. data/lib/sequel_core/dataset.rb +0 -457
  29. data/lib/sequel_core/dataset/callback.rb +0 -13
  30. data/lib/sequel_core/dataset/convenience.rb +0 -245
  31. data/lib/sequel_core/dataset/pagination.rb +0 -96
  32. data/lib/sequel_core/dataset/query.rb +0 -41
  33. data/lib/sequel_core/dataset/schema.rb +0 -15
  34. data/lib/sequel_core/dataset/sql.rb +0 -889
  35. data/lib/sequel_core/deprecated.rb +0 -26
  36. data/lib/sequel_core/exceptions.rb +0 -42
  37. data/lib/sequel_core/migration.rb +0 -187
  38. data/lib/sequel_core/object_graph.rb +0 -216
  39. data/lib/sequel_core/pretty_table.rb +0 -71
  40. data/lib/sequel_core/schema.rb +0 -2
  41. data/lib/sequel_core/schema/generator.rb +0 -239
  42. data/lib/sequel_core/schema/sql.rb +0 -326
  43. data/lib/sequel_core/sql.rb +0 -812
  44. data/lib/sequel_core/worker.rb +0 -68
  45. data/spec/adapters/informix_spec.rb +0 -96
  46. data/spec/adapters/mysql_spec.rb +0 -765
  47. data/spec/adapters/oracle_spec.rb +0 -222
  48. data/spec/adapters/postgres_spec.rb +0 -441
  49. data/spec/adapters/sqlite_spec.rb +0 -413
  50. data/spec/connection_pool_spec.rb +0 -363
  51. data/spec/core_ext_spec.rb +0 -156
  52. data/spec/core_sql_spec.rb +0 -427
  53. data/spec/database_spec.rb +0 -963
  54. data/spec/dataset_spec.rb +0 -2933
  55. data/spec/expression_filters_spec.rb +0 -316
  56. data/spec/migration_spec.rb +0 -261
  57. data/spec/object_graph_spec.rb +0 -230
  58. data/spec/pretty_table_spec.rb +0 -58
  59. data/spec/rcov.opts +0 -6
  60. data/spec/schema_generator_spec.rb +0 -122
  61. data/spec/schema_spec.rb +0 -422
  62. data/spec/spec.opts +0 -0
  63. data/spec/spec_config.rb +0 -7
  64. data/spec/spec_config.rb.example +0 -8
  65. data/spec/spec_helper.rb +0 -55
  66. 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