colincasey-sequel 2.10.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +7 -1
  2. data/doc/advanced_associations.rdoc +614 -0
  3. data/doc/cheat_sheet.rdoc +223 -0
  4. data/doc/dataset_filtering.rdoc +158 -0
  5. data/doc/prepared_statements.rdoc +104 -0
  6. data/doc/release_notes/1.0.txt +38 -0
  7. data/doc/release_notes/1.1.txt +143 -0
  8. data/doc/release_notes/1.3.txt +101 -0
  9. data/doc/release_notes/1.4.0.txt +53 -0
  10. data/doc/release_notes/1.5.0.txt +155 -0
  11. data/doc/release_notes/2.0.0.txt +298 -0
  12. data/doc/release_notes/2.1.0.txt +271 -0
  13. data/doc/release_notes/2.10.0.txt +328 -0
  14. data/doc/release_notes/2.2.0.txt +253 -0
  15. data/doc/release_notes/2.3.0.txt +88 -0
  16. data/doc/release_notes/2.4.0.txt +106 -0
  17. data/doc/release_notes/2.5.0.txt +137 -0
  18. data/doc/release_notes/2.6.0.txt +157 -0
  19. data/doc/release_notes/2.7.0.txt +166 -0
  20. data/doc/release_notes/2.8.0.txt +171 -0
  21. data/doc/release_notes/2.9.0.txt +97 -0
  22. data/doc/schema.rdoc +29 -0
  23. data/doc/sharding.rdoc +113 -0
  24. data/lib/sequel.rb +1 -0
  25. data/lib/sequel_core/adapters/ado.rb +89 -0
  26. data/lib/sequel_core/adapters/db2.rb +143 -0
  27. data/lib/sequel_core/adapters/dbi.rb +112 -0
  28. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  29. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  30. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  31. data/lib/sequel_core/adapters/do.rb +205 -0
  32. data/lib/sequel_core/adapters/firebird.rb +298 -0
  33. data/lib/sequel_core/adapters/informix.rb +85 -0
  34. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  35. data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
  36. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  37. data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
  38. data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
  39. data/lib/sequel_core/adapters/jdbc.rb +491 -0
  40. data/lib/sequel_core/adapters/mysql.rb +369 -0
  41. data/lib/sequel_core/adapters/odbc.rb +174 -0
  42. data/lib/sequel_core/adapters/openbase.rb +68 -0
  43. data/lib/sequel_core/adapters/oracle.rb +107 -0
  44. data/lib/sequel_core/adapters/postgres.rb +456 -0
  45. data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
  46. data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
  47. data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
  48. data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
  49. data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
  50. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  51. data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
  52. data/lib/sequel_core/adapters/sqlite.rb +248 -0
  53. data/lib/sequel_core/connection_pool.rb +258 -0
  54. data/lib/sequel_core/core_ext.rb +217 -0
  55. data/lib/sequel_core/core_sql.rb +202 -0
  56. data/lib/sequel_core/database/schema.rb +164 -0
  57. data/lib/sequel_core/database.rb +691 -0
  58. data/lib/sequel_core/dataset/callback.rb +13 -0
  59. data/lib/sequel_core/dataset/convenience.rb +237 -0
  60. data/lib/sequel_core/dataset/pagination.rb +96 -0
  61. data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
  62. data/lib/sequel_core/dataset/query.rb +41 -0
  63. data/lib/sequel_core/dataset/schema.rb +15 -0
  64. data/lib/sequel_core/dataset/sql.rb +1010 -0
  65. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  66. data/lib/sequel_core/dataset/unsupported.rb +43 -0
  67. data/lib/sequel_core/dataset.rb +511 -0
  68. data/lib/sequel_core/deprecated.rb +26 -0
  69. data/lib/sequel_core/exceptions.rb +44 -0
  70. data/lib/sequel_core/migration.rb +212 -0
  71. data/lib/sequel_core/object_graph.rb +230 -0
  72. data/lib/sequel_core/pretty_table.rb +71 -0
  73. data/lib/sequel_core/schema/generator.rb +320 -0
  74. data/lib/sequel_core/schema/sql.rb +325 -0
  75. data/lib/sequel_core/schema.rb +2 -0
  76. data/lib/sequel_core/sql.rb +887 -0
  77. data/lib/sequel_core/version.rb +11 -0
  78. data/lib/sequel_core.rb +172 -0
  79. data/lib/sequel_model/association_reflection.rb +267 -0
  80. data/lib/sequel_model/associations.rb +499 -0
  81. data/lib/sequel_model/base.rb +523 -0
  82. data/lib/sequel_model/caching.rb +82 -0
  83. data/lib/sequel_model/dataset_methods.rb +26 -0
  84. data/lib/sequel_model/eager_loading.rb +370 -0
  85. data/lib/sequel_model/exceptions.rb +7 -0
  86. data/lib/sequel_model/hooks.rb +101 -0
  87. data/lib/sequel_model/inflector.rb +281 -0
  88. data/lib/sequel_model/plugins.rb +62 -0
  89. data/lib/sequel_model/record.rb +568 -0
  90. data/lib/sequel_model/schema.rb +49 -0
  91. data/lib/sequel_model/validations.rb +429 -0
  92. data/lib/sequel_model.rb +91 -0
  93. data/spec/adapters/ado_spec.rb +46 -0
  94. data/spec/adapters/firebird_spec.rb +376 -0
  95. data/spec/adapters/informix_spec.rb +96 -0
  96. data/spec/adapters/mysql_spec.rb +881 -0
  97. data/spec/adapters/oracle_spec.rb +244 -0
  98. data/spec/adapters/postgres_spec.rb +687 -0
  99. data/spec/adapters/spec_helper.rb +10 -0
  100. data/spec/adapters/sqlite_spec.rb +555 -0
  101. data/spec/integration/dataset_test.rb +134 -0
  102. data/spec/integration/eager_loader_test.rb +696 -0
  103. data/spec/integration/prepared_statement_test.rb +130 -0
  104. data/spec/integration/schema_test.rb +180 -0
  105. data/spec/integration/spec_helper.rb +58 -0
  106. data/spec/integration/type_test.rb +96 -0
  107. data/spec/rcov.opts +6 -0
  108. data/spec/sequel_core/connection_pool_spec.rb +526 -0
  109. data/spec/sequel_core/core_ext_spec.rb +156 -0
  110. data/spec/sequel_core/core_sql_spec.rb +522 -0
  111. data/spec/sequel_core/database_spec.rb +1188 -0
  112. data/spec/sequel_core/dataset_spec.rb +3481 -0
  113. data/spec/sequel_core/expression_filters_spec.rb +363 -0
  114. data/spec/sequel_core/migration_spec.rb +261 -0
  115. data/spec/sequel_core/object_graph_spec.rb +272 -0
  116. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  117. data/spec/sequel_core/schema_generator_spec.rb +167 -0
  118. data/spec/sequel_core/schema_spec.rb +780 -0
  119. data/spec/sequel_core/spec_helper.rb +55 -0
  120. data/spec/sequel_core/version_spec.rb +7 -0
  121. data/spec/sequel_model/association_reflection_spec.rb +93 -0
  122. data/spec/sequel_model/associations_spec.rb +1767 -0
  123. data/spec/sequel_model/base_spec.rb +419 -0
  124. data/spec/sequel_model/caching_spec.rb +215 -0
  125. data/spec/sequel_model/dataset_methods_spec.rb +78 -0
  126. data/spec/sequel_model/eager_loading_spec.rb +1165 -0
  127. data/spec/sequel_model/hooks_spec.rb +485 -0
  128. data/spec/sequel_model/inflector_spec.rb +119 -0
  129. data/spec/sequel_model/model_spec.rb +588 -0
  130. data/spec/sequel_model/plugins_spec.rb +80 -0
  131. data/spec/sequel_model/record_spec.rb +1184 -0
  132. data/spec/sequel_model/schema_spec.rb +90 -0
  133. data/spec/sequel_model/spec_helper.rb +78 -0
  134. data/spec/sequel_model/validations_spec.rb +1067 -0
  135. data/spec/spec.opts +0 -0
  136. data/spec/spec_config.rb.example +10 -0
  137. metadata +177 -3
@@ -0,0 +1,237 @@
1
+ module Sequel
2
+ class Dataset
3
+ COMMA_SEPARATOR = ', '.freeze
4
+ COUNT_OF_ALL_AS_COUNT = SQL::Function.new(: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{|o| o.avg(column)}
20
+ end
21
+
22
+ # Returns true if no records exists in the dataset
23
+ def empty?
24
+ 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{|o| o.id > 2} => {:id=>5}
44
+ # ds.order(:id).first{|o| o.id > 2} => {:id=>3}
45
+ # ds.first{|o| o.id > 2} => {:id=>5}
46
+ # ds.first("id > ?", 4){|o| o.id < 6} => {:id=>5}
47
+ # ds.order(:id).first(2){|o| o.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=nil, &block)
65
+ raise(Error, 'must provide argument or block to Dataset#get, not both') if column && block
66
+ (column ? select(column) : select(&block)).single_value
67
+ end
68
+
69
+ # Returns a dataset grouped by the given column with count by group.
70
+ def group_and_count(*columns)
71
+ group(*columns).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
72
+ end
73
+
74
+ # Returns the interval between minimum and maximum values for the given
75
+ # column.
76
+ def interval(column)
77
+ get{|o| o.max(column) - o.min(column)}
78
+ end
79
+
80
+ # Reverses the order and then runs first. Note that this
81
+ # will not necessarily give you the last record in the dataset,
82
+ # unless you have an unambiguous order. If there is not
83
+ # currently an order for this dataset, raises an Error.
84
+ def last(*args, &block)
85
+ raise(Error, 'No order specified') unless @opts[:order]
86
+ reverse.first(*args, &block)
87
+ end
88
+
89
+ # Maps column values for each record in the dataset (if a column name is
90
+ # given), or performs the stock mapping functionality of Enumerable.
91
+ def map(column_name = nil, &block)
92
+ if column_name
93
+ super() {|r| r[column_name]}
94
+ else
95
+ super(&block)
96
+ end
97
+ end
98
+
99
+ # Returns the maximum value for the given column.
100
+ def max(column)
101
+ get{|o| o.max(column)}
102
+ end
103
+
104
+ # Returns the minimum value for the given column.
105
+ def min(column)
106
+ get{|o| o.min(column)}
107
+ end
108
+
109
+ # Inserts multiple records into the associated table. This method can be
110
+ # to efficiently insert a large amounts of records into a table. Inserts
111
+ # are automatically wrapped in a transaction.
112
+ #
113
+ # This method should be called with a columns array and an array of value arrays:
114
+ #
115
+ # dataset.multi_insert([:x, :y], [[1, 2], [3, 4]])
116
+ #
117
+ # This method can also be called with an array of hashes:
118
+ #
119
+ # dataset.multi_insert({:x => 1}, {:x => 2})
120
+ #
121
+ # Be aware that all hashes should have the same keys if you use this calling method,
122
+ # otherwise some columns could be missed or set to null instead of to default
123
+ # values.
124
+ #
125
+ # The method also accepts a :slice or :commit_every option that specifies
126
+ # the number of records to insert per transaction. This is useful especially
127
+ # when inserting a large number of records, e.g.:
128
+ #
129
+ # # this will commit every 50 records
130
+ # dataset.multi_insert(lots_of_records, :slice => 50)
131
+ def multi_insert(*args)
132
+ if args.empty?
133
+ return
134
+ elsif args[0].is_a?(Array) && args[1].is_a?(Array)
135
+ columns, values, opts = *args
136
+ elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
137
+ table = @opts[:from].first
138
+ columns, dataset = *args
139
+ sql = "INSERT INTO #{quote_identifier(table)} (#{identifier_list(columns)}) VALUES #{literal(dataset)}"
140
+ return @db.transaction{execute_dui(sql)}
141
+ else
142
+ # we assume that an array of hashes is given
143
+ hashes, opts = *args
144
+ return if hashes.empty?
145
+ columns = hashes.first.keys
146
+ # convert the hashes into arrays
147
+ values = hashes.map {|h| columns.map {|c| h[c]}}
148
+ end
149
+ # make sure there's work to do
150
+ return if columns.empty? || values.empty?
151
+
152
+ slice_size = opts && (opts[:commit_every] || opts[:slice])
153
+
154
+ if slice_size
155
+ values.each_slice(slice_size) do |slice|
156
+ statements = multi_insert_sql(columns, slice)
157
+ @db.transaction{statements.each{|st| execute_dui(st)}}
158
+ end
159
+ else
160
+ statements = multi_insert_sql(columns, values)
161
+ @db.transaction{statements.each{|st| execute_dui(st)}}
162
+ end
163
+ end
164
+ alias_method :import, :multi_insert
165
+
166
+ # Pretty prints the records in the dataset as plain-text table.
167
+ def print(*cols)
168
+ Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
169
+ end
170
+
171
+ # Returns a Range object made from the minimum and maximum values for the
172
+ # given column.
173
+ def range(column)
174
+ if r = select{|o| [o.min(column).as(:v1), o.max(column).as(:v2)]}.first
175
+ (r[:v1]..r[:v2])
176
+ end
177
+ end
178
+
179
+ # Returns the first record in the dataset.
180
+ def single_record(opts = nil)
181
+ each((opts||{}).merge(:limit=>1)){|r| return r}
182
+ nil
183
+ end
184
+
185
+ # Returns the first value of the first record in the dataset.
186
+ # Returns nil if dataset is empty.
187
+ def single_value(opts = nil)
188
+ if r = single_record((opts||{}).merge(:graph=>false, :naked=>true))
189
+ r.values.first
190
+ end
191
+ end
192
+
193
+ # Returns the sum for the given column.
194
+ def sum(column)
195
+ get{|o| o.sum(column)}
196
+ end
197
+
198
+ # Returns true if the table exists. Will raise an error
199
+ # if the dataset has fixed SQL or selects from another dataset
200
+ # or more than one table.
201
+ def table_exists?
202
+ raise(Sequel::Error, "this dataset has fixed SQL") if @opts[:sql]
203
+ raise(Sequel::Error, "this dataset selects from multiple sources") if @opts[:from].size != 1
204
+ t = @opts[:from].first
205
+ raise(Sequel::Error, "this dataset selects from a sub query") if t.is_a?(Dataset)
206
+ @db.table_exists?(t)
207
+ end
208
+
209
+ # Returns a string in CSV format containing the dataset records. By
210
+ # default the CSV representation includes the column titles in the
211
+ # first line. You can turn that off by passing false as the
212
+ # include_column_titles argument.
213
+ #
214
+ # This does not use a CSV library or handle quoting of values in
215
+ # any way. If any values in any of the rows could include commas or line
216
+ # endings, you probably shouldn't use this.
217
+ def to_csv(include_column_titles = true)
218
+ n = naked
219
+ cols = n.columns
220
+ csv = ''
221
+ csv << "#{cols.join(COMMA_SEPARATOR)}\r\n" if include_column_titles
222
+ n.each{|r| csv << "#{cols.collect{|c| r[c]}.join(COMMA_SEPARATOR)}\r\n"}
223
+ csv
224
+ end
225
+
226
+ # Returns a hash with one column used as key and another used as value.
227
+ # If rows have duplicate values for the key column, the latter row(s)
228
+ # will overwrite the value of the previous row(s). If the value_column
229
+ # is not given or nil, uses the entire hash as the value.
230
+ def to_hash(key_column, value_column = nil)
231
+ inject({}) do |m, r|
232
+ m[r[key_column]] = value_column ? r[value_column] : r
233
+ m
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,96 @@
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
@@ -0,0 +1,220 @@
1
+ module Sequel
2
+ class Dataset
3
+ PREPARED_ARG_PLACEHOLDER = '?'.lit.freeze
4
+
5
+ # Default implementation of the argument mapper to allow
6
+ # native database support for bind variables and prepared
7
+ # statements (as opposed to the emulated ones used by default).
8
+ module ArgumentMapper
9
+ SQL_QUERY_TYPE = Hash.new{|h,k| h[k] = k}
10
+ SQL_QUERY_TYPE[:first] = SQL_QUERY_TYPE[:all] = :select
11
+
12
+ # The name of the prepared statement, if any.
13
+ attr_accessor :prepared_statement_name
14
+
15
+ # The bind arguments to use for running this prepared statement
16
+ attr_accessor :bind_arguments
17
+
18
+ # Set the bind arguments based on the hash and call super.
19
+ def call(hash, &block)
20
+ ds = clone
21
+ ds.prepared_sql
22
+ ds.bind_arguments = ds.map_to_prepared_args(hash)
23
+ ds.prepared_args = hash
24
+ ds.run(&block)
25
+ end
26
+
27
+ # Override the given *_sql method based on the type, and
28
+ # cache the result of the sql.
29
+ def prepared_sql
30
+ return @prepared_sql if @prepared_sql
31
+ @prepared_args ||= []
32
+ @prepared_sql = super
33
+ meta_def("#{sql_query_type}_sql"){|*args| prepared_sql}
34
+ @prepared_sql
35
+ end
36
+
37
+ private
38
+
39
+ # The type of query (:select, :insert, :delete, :update).
40
+ def sql_query_type
41
+ SQL_QUERY_TYPE[@prepared_type]
42
+ end
43
+ end
44
+
45
+ # Backbone of the prepared statement support. Grafts bind variable
46
+ # support into datasets by hijacking #literal and using placeholders.
47
+ # By default, emulates prepared statements and bind variables by
48
+ # taking the hash of bind variables and directly substituting them
49
+ # into the query, which works on all databases, as it is no different
50
+ # from using the dataset without bind variables.
51
+ module PreparedStatementMethods
52
+ PLACEHOLDER_RE = /\A\$(.*)\z/
53
+
54
+ # The type of prepared statement, should be one of :select, :first,
55
+ # :insert, :update, or :delete
56
+ attr_accessor :prepared_type
57
+
58
+ # The bind variable hash to use when substituting
59
+ attr_accessor :prepared_args
60
+
61
+ # The argument to supply to insert and update, which may use
62
+ # placeholders specified by prepared_args
63
+ attr_accessor :prepared_modify_values
64
+
65
+ # Sets the prepared_args to the given hash and runs the
66
+ # prepared statement.
67
+ def call(hash, &block)
68
+ ds = clone
69
+ ds.prepared_args = hash
70
+ ds.run(&block)
71
+ end
72
+
73
+ # Returns the SQL for the prepared statement, depending on
74
+ # the type of the statement and the prepared_modify_values.
75
+ def prepared_sql
76
+ case @prepared_type
77
+ when :select, :all
78
+ select_sql
79
+ when :first
80
+ select_sql(:limit=>1)
81
+ when :insert
82
+ insert_sql(@prepared_modify_values)
83
+ when :update
84
+ update_sql(@prepared_modify_values)
85
+ when :delete
86
+ delete_sql
87
+ end
88
+ end
89
+
90
+ # Changes the values of symbols if they start with $ and
91
+ # prepared_args is present. If so, they are considered placeholders,
92
+ # and they are substituted using prepared_arg.
93
+ def literal(v)
94
+ case v
95
+ when Symbol
96
+ if match = PLACEHOLDER_RE.match(v.to_s) and @prepared_args
97
+ super(prepared_arg(match[1].to_sym))
98
+ else
99
+ super
100
+ end
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ # Programmer friendly string showing this is a prepared statement,
107
+ # with the prepared SQL it represents (which in general won't have
108
+ # substituted variables).
109
+ def inspect
110
+ "<#{self.class.name}/PreparedStatement #{prepared_sql.inspect}>"
111
+ end
112
+
113
+ protected
114
+
115
+ # Run the method based on the type of prepared statement, with
116
+ # :select running #all to get all of the rows, and the other
117
+ # types running the method with the same name as the type.
118
+ def run(&block)
119
+ case @prepared_type
120
+ when :select, :all
121
+ all(&block)
122
+ when :first
123
+ first
124
+ when :insert
125
+ insert(@prepared_modify_values)
126
+ when :update
127
+ update(@prepared_modify_values)
128
+ when :delete
129
+ delete
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ # Returns the value of the prepared_args hash for the given key.
136
+ def prepared_arg(k)
137
+ @prepared_args[k]
138
+ end
139
+
140
+ # Use a clone of the dataset extended with prepared statement
141
+ # support and using the same argument hash so that you can use
142
+ # bind variables/prepared arguments in subselects.
143
+ def subselect_sql(ds)
144
+ ps = ds.prepare(:select)
145
+ ps.prepared_args = prepared_args
146
+ ps.prepared_sql
147
+ end
148
+ end
149
+
150
+ # Default implementation for an argument mapper that uses
151
+ # unnumbered SQL placeholder arguments. Keeps track of which
152
+ # arguments have been used, and allows arguments to
153
+ # be used more than once.
154
+ module UnnumberedArgumentMapper
155
+ include ArgumentMapper
156
+
157
+ protected
158
+
159
+ # Returns a single output array mapping the values of the input hash.
160
+ # Keys in the input hash that are used more than once in the query
161
+ # have multiple entries in the output array.
162
+ def map_to_prepared_args(hash)
163
+ @prepared_args.map{|v| hash[v]}
164
+ end
165
+
166
+ private
167
+
168
+ # Associates the argument with name k with the next position in
169
+ # the output array.
170
+ def prepared_arg(k)
171
+ @prepared_args << k
172
+ prepared_arg_placeholder
173
+ end
174
+ end
175
+
176
+ # For the given type (:select, :insert, :update, or :delete),
177
+ # run the sql with the bind variables
178
+ # specified in the hash. values is a hash of passed to
179
+ # insert or update (if one of those types is used),
180
+ # which may contain placeholders.
181
+ def call(type, bind_variables={}, values=nil)
182
+ prepare(type, nil, values).call(bind_variables)
183
+ end
184
+
185
+ # Prepare an SQL statement for later execution. This returns
186
+ # a clone of the dataset extended with PreparedStatementMethods,
187
+ # on which you can call call with the hash of bind variables to
188
+ # do substitution. The prepared statement is also stored in
189
+ # the associated database. The following usage is identical:
190
+ #
191
+ # ps = prepare(:select, :select_by_name)
192
+ # ps.call(:name=>'Blah')
193
+ # db.call(:select_by_name, :name=>'Blah')
194
+ def prepare(type, name=nil, values=nil)
195
+ ps = to_prepared_statement(type, values)
196
+ db.prepared_statements[name] = ps if name
197
+ ps
198
+ end
199
+
200
+ protected
201
+
202
+ # Return a cloned copy of the current dataset extended with
203
+ # PreparedStatementMethods, setting the type and modify values.
204
+ def to_prepared_statement(type, values=nil)
205
+ ps = clone
206
+ ps.extend(PreparedStatementMethods)
207
+ ps.prepared_type = type
208
+ ps.prepared_modify_values = values
209
+ ps
210
+ end
211
+
212
+ private
213
+
214
+ # The argument placeholder. Most databases used unnumbered
215
+ # arguments with question marks, so that is the default.
216
+ def prepared_arg_placeholder
217
+ PREPARED_ARG_PLACEHOLDER
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,41 @@
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{|o| (o.x > 1) & (o.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{|o| (o.x > 1) & (o.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
@@ -0,0 +1,15 @@
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