sequel_core 2.2.0 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
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