viking-sequel 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,64 @@
1
+ module Sequel
2
+ class Dataset
3
+ # ---------------------
4
+ # :section: Mutation methods
5
+ # These methods modify the receiving dataset and should be used with care.
6
+ # ---------------------
7
+
8
+ # All methods that should have a ! method added that modifies
9
+ # the receiver.
10
+ MUTATION_METHODS = %w'add_graph_aliases and cross_join distinct except exclude
11
+ filter for_update from from_self full_join full_outer_join graph
12
+ group group_and_count group_by having inner_join intersect invert join join_table left_join
13
+ left_outer_join limit lock_style naked natural_full_join natural_join
14
+ natural_left_join natural_right_join or order order_by order_more paginate qualify query
15
+ reverse reverse_order right_join right_outer_join select select_all select_append select_more server
16
+ set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
17
+ unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym}
18
+
19
+ # Setup mutation (e.g. filter!) methods. These operate the same as the
20
+ # non-! methods, but replace the options of the current dataset with the
21
+ # options of the resulting dataset.
22
+ def self.def_mutation_method(*meths)
23
+ meths.each do |meth|
24
+ class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
25
+ end
26
+ end
27
+
28
+ # Add the mutation methods via metaprogramming
29
+ def_mutation_method(*MUTATION_METHODS)
30
+
31
+
32
+ # Set the method to call on identifiers going into the database for this dataset
33
+ attr_accessor :identifier_input_method
34
+
35
+ # Set the method to call on identifiers coming the database for this dataset
36
+ attr_accessor :identifier_output_method
37
+
38
+ # Whether to quote identifiers for this dataset
39
+ attr_writer :quote_identifiers
40
+
41
+ # The row_proc for this database, should be a Proc that takes
42
+ # a single hash argument and returns the object you want
43
+ # each to return.
44
+ attr_accessor :row_proc
45
+
46
+ # Add a mutation method to this dataset instance.
47
+ def def_mutation_method(*meths)
48
+ meths.each do |meth|
49
+ instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Modify the receiver with the results of sending the meth, args, and block
56
+ # to the receiver and merging the options of the resulting dataset into
57
+ # the receiver's options.
58
+ def mutation_method(meth, *args, &block)
59
+ copy = send(meth, *args, &block)
60
+ @opts.merge!(copy.opts)
61
+ self
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,227 @@
1
+ module Sequel
2
+ class Dataset
3
+ # ---------------------
4
+ # :section: Methods related to prepared statements or bound variables
5
+ # On some adapters, these use native prepared statements and bound variables, on others
6
+ # support is emulated. For details, see the {"Prepared Statements/Bound Variables" guide}[link:files/doc/prepared_statements_rdoc.html].
7
+ # ---------------------
8
+
9
+ PREPARED_ARG_PLACEHOLDER = LiteralString.new('?').freeze
10
+
11
+ # Default implementation of the argument mapper to allow
12
+ # native database support for bind variables and prepared
13
+ # statements (as opposed to the emulated ones used by default).
14
+ module ArgumentMapper
15
+ SQL_QUERY_TYPE = Hash.new{|h,k| h[k] = k}
16
+ SQL_QUERY_TYPE[:first] = SQL_QUERY_TYPE[:all] = :select
17
+
18
+ # The name of the prepared statement, if any.
19
+ attr_accessor :prepared_statement_name
20
+
21
+ # The bind arguments to use for running this prepared statement
22
+ attr_accessor :bind_arguments
23
+
24
+ # Set the bind arguments based on the hash and call super.
25
+ def call(bind_vars={}, &block)
26
+ ds = bind(bind_vars)
27
+ ds.prepared_sql
28
+ ds.bind_arguments = ds.map_to_prepared_args(ds.opts[:bind_vars])
29
+ ds.run(&block)
30
+ end
31
+
32
+ # Override the given *_sql method based on the type, and
33
+ # cache the result of the sql.
34
+ def prepared_sql
35
+ return @prepared_sql if @prepared_sql
36
+ @prepared_args ||= []
37
+ @prepared_sql = super
38
+ meta_def("#{sql_query_type}_sql"){|*args| prepared_sql}
39
+ @prepared_sql
40
+ end
41
+
42
+ private
43
+
44
+ # The type of query (:select, :insert, :delete, :update).
45
+ def sql_query_type
46
+ SQL_QUERY_TYPE[@prepared_type]
47
+ end
48
+ end
49
+
50
+ # Backbone of the prepared statement support. Grafts bind variable
51
+ # support into datasets by hijacking #literal and using placeholders.
52
+ # By default, emulates prepared statements and bind variables by
53
+ # taking the hash of bind variables and directly substituting them
54
+ # into the query, which works on all databases, as it is no different
55
+ # from using the dataset without bind variables.
56
+ module PreparedStatementMethods
57
+ PLACEHOLDER_RE = /\A\$(.*)\z/
58
+
59
+ # The type of prepared statement, should be one of :select, :first,
60
+ # :insert, :update, or :delete
61
+ attr_accessor :prepared_type
62
+
63
+ # The array/hash of bound variable placeholder names.
64
+ attr_accessor :prepared_args
65
+
66
+ # The argument to supply to insert and update, which may use
67
+ # placeholders specified by prepared_args
68
+ attr_accessor :prepared_modify_values
69
+
70
+ # Sets the prepared_args to the given hash and runs the
71
+ # prepared statement.
72
+ def call(bind_vars={}, &block)
73
+ bind(bind_vars).run(&block)
74
+ end
75
+
76
+ # Returns the SQL for the prepared statement, depending on
77
+ # the type of the statement and the prepared_modify_values.
78
+ def prepared_sql
79
+ case @prepared_type
80
+ when :select, :all
81
+ select_sql
82
+ when :first
83
+ clone(:limit=>1).select_sql
84
+ when :insert
85
+ insert_sql(*@prepared_modify_values)
86
+ when :update
87
+ update_sql(*@prepared_modify_values)
88
+ when :delete
89
+ delete_sql
90
+ end
91
+ end
92
+
93
+ # Changes the values of symbols if they start with $ and
94
+ # prepared_args is present. If so, they are considered placeholders,
95
+ # and they are substituted using prepared_arg.
96
+ def literal_symbol(v)
97
+ if @opts[:bind_vars] and match = PLACEHOLDER_RE.match(v.to_s)
98
+ v2 = prepared_arg(match[1].to_sym)
99
+ v2 ? literal(v2) : v
100
+ else
101
+ super
102
+ end
103
+ end
104
+
105
+ # Programmer friendly string showing this is a prepared statement,
106
+ # with the prepared SQL it represents (which in general won't have
107
+ # substituted variables).
108
+ def inspect
109
+ "<#{self.class.name}/PreparedStatement #{prepared_sql.inspect}>"
110
+ end
111
+
112
+ protected
113
+
114
+ # Run the method based on the type of prepared statement, with
115
+ # :select running #all to get all of the rows, and the other
116
+ # types running the method with the same name as the type.
117
+ def run(&block)
118
+ case @prepared_type
119
+ when :select, :all
120
+ all(&block)
121
+ when :first
122
+ first
123
+ when :insert
124
+ insert(*@prepared_modify_values)
125
+ when :update
126
+ update(*@prepared_modify_values)
127
+ when :delete
128
+ delete
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ # Returns the value of the prepared_args hash for the given key.
135
+ def prepared_arg(k)
136
+ @opts[:bind_vars][k]
137
+ end
138
+
139
+ # Use a clone of the dataset extended with prepared statement
140
+ # support and using the same argument hash so that you can use
141
+ # bind variables/prepared arguments in subselects.
142
+ def subselect_sql(ds)
143
+ ps = ds.prepare(:select)
144
+ ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
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(bind_vars)
163
+ prepared_args.map{|v| bind_vars[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
+ # Set the bind variables to use for the call. If bind variables have
177
+ # already been set for this dataset, they are updated with the contents
178
+ # of bind_vars.
179
+ def bind(bind_vars={})
180
+ clone(:bind_vars=>@opts[:bind_vars] ? @opts[:bind_vars].merge(bind_vars) : bind_vars)
181
+ end
182
+
183
+ # For the given type (:select, :insert, :update, or :delete),
184
+ # run the sql with the bind variables
185
+ # specified in the hash. values is a hash of passed to
186
+ # insert or update (if one of those types is used),
187
+ # which may contain placeholders.
188
+ def call(type, bind_variables={}, *values, &block)
189
+ prepare(type, nil, *values).call(bind_variables, &block)
190
+ end
191
+
192
+ # Prepare an SQL statement for later execution. This returns
193
+ # a clone of the dataset extended with PreparedStatementMethods,
194
+ # on which you can call call with the hash of bind variables to
195
+ # do substitution. The prepared statement is also stored in
196
+ # the associated database. The following usage is identical:
197
+ #
198
+ # ps = prepare(:select, :select_by_name)
199
+ # ps.call(:name=>'Blah')
200
+ # db.call(:select_by_name, :name=>'Blah')
201
+ def prepare(type, name=nil, *values)
202
+ ps = to_prepared_statement(type, values)
203
+ db.prepared_statements[name] = ps if name
204
+ ps
205
+ end
206
+
207
+ protected
208
+
209
+ # Return a cloned copy of the current dataset extended with
210
+ # PreparedStatementMethods, setting the type and modify values.
211
+ def to_prepared_statement(type, values=nil)
212
+ ps = bind
213
+ ps.extend(PreparedStatementMethods)
214
+ ps.prepared_type = type
215
+ ps.prepared_modify_values = values
216
+ ps
217
+ end
218
+
219
+ private
220
+
221
+ # The argument placeholder. Most databases used unnumbered
222
+ # arguments with question marks, so that is the default.
223
+ def prepared_arg_placeholder
224
+ PREPARED_ARG_PLACEHOLDER
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,709 @@
1
+ module Sequel
2
+ class Dataset
3
+ # ---------------------
4
+ # :section: Methods that return modified datasets
5
+ # These methods all return modified copies of the receiver.
6
+ # ---------------------
7
+ # The dataset options that require the removal of cached columns
8
+ # if changed.
9
+ COLUMN_CHANGE_OPTS = [:select, :sql, :from, :join].freeze
10
+
11
+ # Which options don't affect the SQL generation. Used by simple_select_all?
12
+ # to determine if this is a simple SELECT * FROM table.
13
+ NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager_graph, :graph_aliases]
14
+
15
+ # These symbols have _join methods created (e.g. inner_join) that
16
+ # call join_table with the symbol, passing along the arguments and
17
+ # block from the method call.
18
+ CONDITIONED_JOIN_TYPES = [:inner, :full_outer, :right_outer, :left_outer, :full, :right, :left]
19
+
20
+ # These symbols have _join methods created (e.g. natural_join) that
21
+ # call join_table with the symbol. They only accept a single table
22
+ # argument which is passed to join_table, and they raise an error
23
+ # if called with a block.
24
+ UNCONDITIONED_JOIN_TYPES = [:natural, :natural_left, :natural_right, :natural_full, :cross]
25
+
26
+ # Adds an further filter to an existing filter using AND. If no filter
27
+ # exists an error is raised. This method is identical to #filter except
28
+ # it expects an existing filter.
29
+ #
30
+ # ds.filter(:a).and(:b) # SQL: WHERE a AND b
31
+ def and(*cond, &block)
32
+ raise(InvalidOperation, "No existing filter found.") unless @opts[:having] || @opts[:where]
33
+ filter(*cond, &block)
34
+ end
35
+
36
+ # Returns a new clone of the dataset with with the given options merged.
37
+ # If the options changed include options in COLUMN_CHANGE_OPTS, the cached
38
+ # columns are deleted.
39
+ def clone(opts = {})
40
+ c = super()
41
+ c.opts = @opts.merge(opts)
42
+ c.instance_variable_set(:@columns, nil) if opts.keys.any?{|o| COLUMN_CHANGE_OPTS.include?(o)}
43
+ c
44
+ end
45
+
46
+ # Returns a copy of the dataset with the SQL DISTINCT clause.
47
+ # The DISTINCT clause is used to remove duplicate rows from the
48
+ # output. If arguments are provided, uses a DISTINCT ON clause,
49
+ # in which case it will only be distinct on those columns, instead
50
+ # of all returned columns. Raises an error if arguments
51
+ # are given and DISTINCT ON is not supported.
52
+ #
53
+ # dataset.distinct # SQL: SELECT DISTINCT * FROM items
54
+ # dataset.order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
55
+ def distinct(*args)
56
+ raise(InvalidOperation, "DISTINCT ON not supported") if !args.empty? && !supports_distinct_on?
57
+ clone(:distinct => args)
58
+ end
59
+
60
+ # Adds an EXCEPT clause using a second dataset object.
61
+ # An EXCEPT compound dataset returns all rows in the current dataset
62
+ # that are not in the given dataset.
63
+ # Raises an InvalidOperation if the operation is not supported.
64
+ # Options:
65
+ # * :all - Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
66
+ # * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
67
+ #
68
+ # DB[:items].except(DB[:other_items]).sql
69
+ # #=> "SELECT * FROM items EXCEPT SELECT * FROM other_items"
70
+ def except(dataset, opts={})
71
+ opts = {:all=>opts} unless opts.is_a?(Hash)
72
+ raise(InvalidOperation, "EXCEPT not supported") unless supports_intersect_except?
73
+ raise(InvalidOperation, "EXCEPT ALL not supported") if opts[:all] && !supports_intersect_except_all?
74
+ compound_clone(:except, dataset, opts)
75
+ end
76
+
77
+ # Performs the inverse of Dataset#filter.
78
+ #
79
+ # dataset.exclude(:category => 'software').sql #=>
80
+ # "SELECT * FROM items WHERE (category != 'software')"
81
+ def exclude(*cond, &block)
82
+ clause = (@opts[:having] ? :having : :where)
83
+ cond = cond.first if cond.size == 1
84
+ cond = filter_expr(cond, &block)
85
+ cond = SQL::BooleanExpression.invert(cond)
86
+ cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
87
+ clone(clause => cond)
88
+ end
89
+
90
+ # Returns a copy of the dataset with the given conditions imposed upon it.
91
+ # If the query already has a HAVING clause, then the conditions are imposed in the
92
+ # HAVING clause. If not, then they are imposed in the WHERE clause.
93
+ #
94
+ # filter accepts the following argument types:
95
+ #
96
+ # * Hash - list of equality/inclusion expressions
97
+ # * Array - depends:
98
+ # * If first member is a string, assumes the rest of the arguments
99
+ # are parameters and interpolates them into the string.
100
+ # * If all members are arrays of length two, treats the same way
101
+ # as a hash, except it allows for duplicate keys to be
102
+ # specified.
103
+ # * String - taken literally
104
+ # * Symbol - taken as a boolean column argument (e.g. WHERE active)
105
+ # * Sequel::SQL::BooleanExpression - an existing condition expression,
106
+ # probably created using the Sequel expression filter DSL.
107
+ #
108
+ # filter also takes a block, which should return one of the above argument
109
+ # types, and is treated the same way. This block yields a virtual row object,
110
+ # which is easy to use to create identifiers and functions. For more details
111
+ # on the virtual row support, see the {"Virtual Rows" guide}[link:files/doc/virtual_rows_rdoc.html]
112
+ #
113
+ # If both a block and regular argument
114
+ # are provided, they get ANDed together.
115
+ #
116
+ # Examples:
117
+ #
118
+ # dataset.filter(:id => 3).sql #=>
119
+ # "SELECT * FROM items WHERE (id = 3)"
120
+ # dataset.filter('price < ?', 100).sql #=>
121
+ # "SELECT * FROM items WHERE price < 100"
122
+ # dataset.filter([[:id, (1,2,3)], [:id, 0..10]]).sql #=>
123
+ # "SELECT * FROM items WHERE ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))"
124
+ # dataset.filter('price < 100').sql #=>
125
+ # "SELECT * FROM items WHERE price < 100"
126
+ # dataset.filter(:active).sql #=>
127
+ # "SELECT * FROM items WHERE :active
128
+ # dataset.filter{|o| o.price < 100}.sql #=>
129
+ # "SELECT * FROM items WHERE (price < 100)"
130
+ #
131
+ # Multiple filter calls can be chained for scoping:
132
+ #
133
+ # software = dataset.filter(:category => 'software')
134
+ # software.filter{|o| o.price < 100}.sql #=>
135
+ # "SELECT * FROM items WHERE ((category = 'software') AND (price < 100))"
136
+ #
137
+ # See the the {"Dataset Filtering" guide}[link:files/doc/dataset_filtering_rdoc.html] for more examples and details.
138
+ def filter(*cond, &block)
139
+ _filter(@opts[:having] ? :having : :where, *cond, &block)
140
+ end
141
+
142
+ # Returns a cloned dataset with a :update lock style.
143
+ def for_update
144
+ lock_style(:update)
145
+ end
146
+
147
+ # Returns a copy of the dataset with the source changed.
148
+ #
149
+ # dataset.from # SQL: SELECT *
150
+ # dataset.from(:blah) # SQL: SELECT * FROM blah
151
+ # dataset.from(:blah, :foo) # SQL: SELECT * FROM blah, foo
152
+ def from(*source)
153
+ table_alias_num = 0
154
+ sources = []
155
+ source.each do |s|
156
+ case s
157
+ when Hash
158
+ s.each{|k,v| sources << SQL::AliasedExpression.new(k,v)}
159
+ when Dataset
160
+ sources << SQL::AliasedExpression.new(s, dataset_alias(table_alias_num+=1))
161
+ when Symbol
162
+ sch, table, aliaz = split_symbol(s)
163
+ if aliaz
164
+ s = sch ? SQL::QualifiedIdentifier.new(sch.to_sym, table.to_sym) : SQL::Identifier.new(table.to_sym)
165
+ sources << SQL::AliasedExpression.new(s, aliaz.to_sym)
166
+ else
167
+ sources << s
168
+ end
169
+ else
170
+ sources << s
171
+ end
172
+ end
173
+ o = {:from=>sources.empty? ? nil : sources}
174
+ o[:num_dataset_sources] = table_alias_num if table_alias_num > 0
175
+ clone(o)
176
+ end
177
+
178
+ # Returns a dataset selecting from the current dataset.
179
+ # Supplying the :alias option controls the name of the result.
180
+ #
181
+ # ds = DB[:items].order(:name).select(:id, :name)
182
+ # ds.sql #=> "SELECT id,name FROM items ORDER BY name"
183
+ # ds.from_self.sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1"
184
+ # ds.from_self(:alias=>:foo).sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo"
185
+ def from_self(opts={})
186
+ fs = {}
187
+ @opts.keys.each{|k| fs[k] = nil unless NON_SQL_OPTIONS.include?(k)}
188
+ clone(fs).from(opts[:alias] ? as(opts[:alias]) : self)
189
+ end
190
+
191
+ # Pattern match any of the columns to any of the terms. The terms can be
192
+ # strings (which use LIKE) or regular expressions (which are only supported
193
+ # in some databases). See Sequel::SQL::StringExpression.like. Note that the
194
+ # total number of pattern matches will be cols.length * terms.length,
195
+ # which could cause performance issues.
196
+ #
197
+ # dataset.grep(:a, '%test%') # SQL: SELECT * FROM items WHERE a LIKE '%test%'
198
+ # dataset.grep([:a, :b], %w'%test% foo') # SQL: SELECT * FROM items WHERE a LIKE '%test%' OR a LIKE 'foo' OR b LIKE '%test%' OR b LIKE 'foo'
199
+ def grep(cols, terms)
200
+ filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
201
+ end
202
+
203
+ # Returns a copy of the dataset with the results grouped by the value of
204
+ # the given columns.
205
+ #
206
+ # dataset.group(:id) # SELECT * FROM items GROUP BY id
207
+ # dataset.group(:id, :name) # SELECT * FROM items GROUP BY id, name
208
+ def group(*columns)
209
+ clone(:group => (columns.compact.empty? ? nil : columns))
210
+ end
211
+ alias group_by group
212
+
213
+ # Returns a dataset grouped by the given column with count by group,
214
+ # order by the count of records. Column aliases may be supplied, and will
215
+ # be included in the select clause.
216
+ #
217
+ # Examples:
218
+ #
219
+ # ds.group_and_count(:name).all => [{:name=>'a', :count=>1}, ...]
220
+ # ds.group_and_count(:first_name, :last_name).all => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
221
+ # ds.group_and_count(:first_name___name).all => [{:name=>'a', :count=>1}, ...]
222
+ def group_and_count(*columns)
223
+ group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
224
+ end
225
+
226
+ # Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
227
+ #
228
+ # dataset.group(:sum).having(:sum=>10) # SQL: SELECT * FROM items GROUP BY sum HAVING sum = 10
229
+ def having(*cond, &block)
230
+ _filter(:having, *cond, &block)
231
+ end
232
+
233
+ # Adds an INTERSECT clause using a second dataset object.
234
+ # An INTERSECT compound dataset returns all rows in both the current dataset
235
+ # and the given dataset.
236
+ # Raises an InvalidOperation if the operation is not supported.
237
+ # Options:
238
+ # * :all - Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
239
+ # * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
240
+ #
241
+ # DB[:items].intersect(DB[:other_items]).sql
242
+ # #=> "SELECT * FROM items INTERSECT SELECT * FROM other_items"
243
+ def intersect(dataset, opts={})
244
+ opts = {:all=>opts} unless opts.is_a?(Hash)
245
+ raise(InvalidOperation, "INTERSECT not supported") unless supports_intersect_except?
246
+ raise(InvalidOperation, "INTERSECT ALL not supported") if opts[:all] && !supports_intersect_except_all?
247
+ compound_clone(:intersect, dataset, opts)
248
+ end
249
+
250
+ # Inverts the current filter
251
+ #
252
+ # dataset.filter(:category => 'software').invert.sql #=>
253
+ # "SELECT * FROM items WHERE (category != 'software')"
254
+ def invert
255
+ having, where = @opts[:having], @opts[:where]
256
+ raise(Error, "No current filter") unless having || where
257
+ o = {}
258
+ o[:having] = SQL::BooleanExpression.invert(having) if having
259
+ o[:where] = SQL::BooleanExpression.invert(where) if where
260
+ clone(o)
261
+ end
262
+
263
+ # Returns a joined dataset. Uses the following arguments:
264
+ #
265
+ # * type - The type of join to do (e.g. :inner)
266
+ # * table - Depends on type:
267
+ # * Dataset - a subselect is performed with an alias of tN for some value of N
268
+ # * Model (or anything responding to :table_name) - table.table_name
269
+ # * String, Symbol: table
270
+ # * expr - specifies conditions, depends on type:
271
+ # * Hash, Array with all two pairs - Assumes key (1st arg) is column of joined table (unless already
272
+ # qualified), and value (2nd arg) is column of the last joined or primary table (or the
273
+ # :implicit_qualifier option).
274
+ # To specify multiple conditions on a single joined table column, you must use an array.
275
+ # Uses a JOIN with an ON clause.
276
+ # * Array - If all members of the array are symbols, considers them as columns and
277
+ # uses a JOIN with a USING clause. Most databases will remove duplicate columns from
278
+ # the result set if this is used.
279
+ # * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
280
+ # or CROSS join. If a block is given, uses a ON clause based on the block, see below.
281
+ # * Everything else - pretty much the same as a using the argument in a call to filter,
282
+ # so strings are considered literal, symbols specify boolean columns, and blockless
283
+ # filter expressions can be used. Uses a JOIN with an ON clause.
284
+ # * options - a hash of options, with any of the following keys:
285
+ # * :table_alias - the name of the table's alias when joining, necessary for joining
286
+ # to the same table more than once. No alias is used by default.
287
+ # * :implicit_qualifier - The name to use for qualifying implicit conditions. By default,
288
+ # the last joined or primary table is used.
289
+ # * block - The block argument should only be given if a JOIN with an ON clause is used,
290
+ # in which case it yields the table alias/name for the table currently being joined,
291
+ # the table alias/name for the last joined (or first table), and an array of previous
292
+ # SQL::JoinClause.
293
+ def join_table(type, table, expr=nil, options={}, &block)
294
+ using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
295
+ if using_join && !supports_join_using?
296
+ h = {}
297
+ expr.each{|s| h[s] = s}
298
+ return join_table(type, table, h, options)
299
+ end
300
+
301
+ case options
302
+ when Hash
303
+ table_alias = options[:table_alias]
304
+ last_alias = options[:implicit_qualifier]
305
+ when Symbol, String, SQL::Identifier
306
+ table_alias = options
307
+ last_alias = nil
308
+ else
309
+ raise Error, "invalid options format for join_table: #{options.inspect}"
310
+ end
311
+
312
+ if Dataset === table
313
+ if table_alias.nil?
314
+ table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
315
+ table_alias = dataset_alias(table_alias_num)
316
+ end
317
+ table_name = table_alias
318
+ else
319
+ table = table.table_name if table.respond_to?(:table_name)
320
+ table_name = table_alias || table
321
+ end
322
+
323
+ join = if expr.nil? and !block_given?
324
+ SQL::JoinClause.new(type, table, table_alias)
325
+ elsif using_join
326
+ raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
327
+ SQL::JoinUsingClause.new(expr, type, table, table_alias)
328
+ else
329
+ last_alias ||= @opts[:last_joined_table] || first_source_alias
330
+ if Sequel.condition_specifier?(expr)
331
+ expr = expr.collect do |k, v|
332
+ k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
333
+ v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
334
+ [k,v]
335
+ end
336
+ end
337
+ if block_given?
338
+ expr2 = yield(table_name, last_alias, @opts[:join] || [])
339
+ expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
340
+ end
341
+ SQL::JoinOnClause.new(expr, type, table, table_alias)
342
+ end
343
+
344
+ opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
345
+ opts[:num_dataset_sources] = table_alias_num if table_alias_num
346
+ clone(opts)
347
+ end
348
+
349
+ CONDITIONED_JOIN_TYPES.each do |jtype|
350
+ class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end", __FILE__, __LINE__)
351
+ end
352
+ UNCONDITIONED_JOIN_TYPES.each do |jtype|
353
+ class_eval("def #{jtype}_join(table); raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?; join_table(:#{jtype}, table) end", __FILE__, __LINE__)
354
+ end
355
+ alias join inner_join
356
+
357
+ # If given an integer, the dataset will contain only the first l results.
358
+ # If given a range, it will contain only those at offsets within that
359
+ # range. If a second argument is given, it is used as an offset.
360
+ #
361
+ # dataset.limit(10) # SQL: SELECT * FROM items LIMIT 10
362
+ # dataset.limit(10, 20) # SQL: SELECT * FROM items LIMIT 10 OFFSET 20
363
+ def limit(l, o = nil)
364
+ return from_self.limit(l, o) if @opts[:sql]
365
+
366
+ if Range === l
367
+ o = l.first
368
+ l = l.last - l.first + (l.exclude_end? ? 0 : 1)
369
+ end
370
+ l = l.to_i if l.is_a?(String) && !l.is_a?(LiteralString)
371
+ if l.is_a?(Integer)
372
+ raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
373
+ end
374
+ opts = {:limit => l}
375
+ if o
376
+ o = o.to_i if o.is_a?(String) && !o.is_a?(LiteralString)
377
+ if o.is_a?(Integer)
378
+ raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
379
+ end
380
+ opts[:offset] = o
381
+ end
382
+ clone(opts)
383
+ end
384
+
385
+ # Returns a cloned dataset with the given lock style. If style is a
386
+ # string, it will be used directly. Otherwise, a symbol may be used
387
+ # for database independent locking. Currently :update is respected
388
+ # by most databases, and :share is supported by some.
389
+ def lock_style(style)
390
+ clone(:lock => style)
391
+ end
392
+
393
+ # Returns a naked dataset clone - i.e. a dataset that returns records as
394
+ # hashes instead of calling the row proc.
395
+ def naked
396
+ ds = clone
397
+ ds.row_proc = nil
398
+ ds
399
+ end
400
+
401
+ # Adds an alternate filter to an existing filter using OR. If no filter
402
+ # exists an error is raised.
403
+ #
404
+ # dataset.filter(:a).or(:b) # SQL: SELECT * FROM items WHERE a OR b
405
+ def or(*cond, &block)
406
+ clause = (@opts[:having] ? :having : :where)
407
+ raise(InvalidOperation, "No existing filter found.") unless @opts[clause]
408
+ cond = cond.first if cond.size == 1
409
+ clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
410
+ end
411
+
412
+ # Returns a copy of the dataset with the order changed. If a nil is given
413
+ # the returned dataset has no order. This can accept multiple arguments
414
+ # of varying kinds, and even SQL functions. If a block is given, it is treated
415
+ # as a virtual row block, similar to filter.
416
+ #
417
+ # ds.order(:name).sql #=> 'SELECT * FROM items ORDER BY name'
418
+ # ds.order(:a, :b).sql #=> 'SELECT * FROM items ORDER BY a, b'
419
+ # ds.order('a + b'.lit).sql #=> 'SELECT * FROM items ORDER BY a + b'
420
+ # ds.order(:a + :b).sql #=> 'SELECT * FROM items ORDER BY (a + b)'
421
+ # ds.order(:name.desc).sql #=> 'SELECT * FROM items ORDER BY name DESC'
422
+ # ds.order(:name.asc).sql #=> 'SELECT * FROM items ORDER BY name ASC'
423
+ # ds.order{|o| o.sum(:name)}.sql #=> 'SELECT * FROM items ORDER BY sum(name)'
424
+ # ds.order(nil).sql #=> 'SELECT * FROM items'
425
+ def order(*columns, &block)
426
+ columns += Array(Sequel.virtual_row(&block)) if block
427
+ clone(:order => (columns.compact.empty?) ? nil : columns)
428
+ end
429
+ alias order_by order
430
+
431
+ # Returns a copy of the dataset with the order columns added
432
+ # to the existing order.
433
+ #
434
+ # ds.order(:a).order(:b).sql #=> 'SELECT * FROM items ORDER BY b'
435
+ # ds.order(:a).order_more(:b).sql #=> 'SELECT * FROM items ORDER BY a, b'
436
+ def order_more(*columns, &block)
437
+ columns = @opts[:order] + columns if @opts[:order]
438
+ order(*columns, &block)
439
+ end
440
+
441
+ # Qualify to the given table, or first source if not table is given.
442
+ def qualify(table=first_source)
443
+ qualify_to(table)
444
+ end
445
+
446
+ # Return a copy of the dataset with unqualified identifiers in the
447
+ # SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
448
+ # given table. If no columns are currently selected, select all
449
+ # columns of the given table.
450
+ def qualify_to(table)
451
+ o = @opts
452
+ return clone if o[:sql]
453
+ h = {}
454
+ (o.keys & QUALIFY_KEYS).each do |k|
455
+ h[k] = qualified_expression(o[k], table)
456
+ end
457
+ h[:select] = [SQL::ColumnAll.new(table)] if !o[:select] || o[:select].empty?
458
+ clone(h)
459
+ end
460
+
461
+ # Qualify the dataset to its current first source. This is useful
462
+ # if you have unqualified identifiers in the query that all refer to
463
+ # the first source, and you want to join to another table which
464
+ # has columns with the same name as columns in the current dataset.
465
+ # See qualify_to.
466
+ def qualify_to_first_source
467
+ qualify_to(first_source)
468
+ end
469
+
470
+ # Returns a copy of the dataset with the order reversed. If no order is
471
+ # given, the existing order is inverted.
472
+ def reverse_order(*order)
473
+ order(*invert_order(order.empty? ? @opts[:order] : order))
474
+ end
475
+ alias reverse reverse_order
476
+
477
+ # Returns a copy of the dataset with the columns selected changed
478
+ # to the given columns. This also takes a virtual row block,
479
+ # similar to filter.
480
+ #
481
+ # dataset.select(:a) # SELECT a FROM items
482
+ # dataset.select(:a, :b) # SELECT a, b FROM items
483
+ # dataset.select{|o| [o.a, o.sum(:b)]} # SELECT a, sum(b) FROM items
484
+ def select(*columns, &block)
485
+ columns += Array(Sequel.virtual_row(&block)) if block
486
+ m = []
487
+ columns.map do |i|
488
+ i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
489
+ end
490
+ clone(:select => m)
491
+ end
492
+
493
+ # Returns a copy of the dataset selecting the wildcard.
494
+ #
495
+ # dataset.select(:a).select_all # SELECT * FROM items
496
+ def select_all
497
+ clone(:select => nil)
498
+ end
499
+
500
+ # Returns a copy of the dataset with the given columns added
501
+ # to the existing selected columns. If no columns are currently selected
502
+ # it will select the columns given in addition to *.
503
+ #
504
+ # dataset.select(:a).select(:b) # SELECT b FROM items
505
+ # dataset.select(:a).select_append(:b) # SELECT a, b FROM items
506
+ # dataset.select_append(:b) # SELECT *, b FROM items
507
+ def select_append(*columns, &block)
508
+ cur_sel = @opts[:select]
509
+ cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
510
+ select(*(cur_sel + columns), &block)
511
+ end
512
+
513
+ # Returns a copy of the dataset with the given columns added
514
+ # to the existing selected columns. If no columns are currently selected
515
+ # it will just select the columns given.
516
+ #
517
+ # dataset.select(:a).select(:b) # SELECT b FROM items
518
+ # dataset.select(:a).select_more(:b) # SELECT a, b FROM items
519
+ # dataset.select_more(:b) # SELECT b FROM items
520
+ def select_more(*columns, &block)
521
+ columns = @opts[:select] + columns if @opts[:select]
522
+ select(*columns, &block)
523
+ end
524
+
525
+ # Set the server for this dataset to use. Used to pick a specific database
526
+ # shard to run a query against, or to override the default (which is SELECT uses
527
+ # :read_only database and all other queries use the :default database).
528
+ def server(servr)
529
+ clone(:server=>servr)
530
+ end
531
+
532
+ # Set the default values for insert and update statements. The values hash passed
533
+ # to insert or update are merged into this hash.
534
+ def set_defaults(hash)
535
+ clone(:defaults=>(@opts[:defaults]||{}).merge(hash))
536
+ end
537
+
538
+ # Set values that override hash arguments given to insert and update statements.
539
+ # This hash is merged into the hash provided to insert or update.
540
+ def set_overrides(hash)
541
+ clone(:overrides=>hash.merge(@opts[:overrides]||{}))
542
+ end
543
+
544
+ # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
545
+ #
546
+ # dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items GROUP BY a
547
+ def unfiltered
548
+ clone(:where => nil, :having => nil)
549
+ end
550
+
551
+ # Returns a copy of the dataset with no grouping (GROUP or HAVING clause) applied.
552
+ #
553
+ # dataset.group(:a).having(:a=>1).where(:b).ungrouped # SELECT * FROM items WHERE b
554
+ def ungrouped
555
+ clone(:group => nil, :having => nil)
556
+ end
557
+
558
+ # Adds a UNION clause using a second dataset object.
559
+ # A UNION compound dataset returns all rows in either the current dataset
560
+ # or the given dataset.
561
+ # Options:
562
+ # * :all - Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
563
+ # * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
564
+ #
565
+ # DB[:items].union(DB[:other_items]).sql
566
+ # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
567
+ def union(dataset, opts={})
568
+ opts = {:all=>opts} unless opts.is_a?(Hash)
569
+ compound_clone(:union, dataset, opts)
570
+ end
571
+
572
+ # Returns a copy of the dataset with no limit or offset.
573
+ #
574
+ # dataset.limit(10, 20).unlimited # SELECT * FROM items
575
+ def unlimited
576
+ clone(:limit=>nil, :offset=>nil)
577
+ end
578
+
579
+ # Returns a copy of the dataset with no order.
580
+ #
581
+ # dataset.order(:a).unordered # SELECT * FROM items
582
+ def unordered
583
+ order(nil)
584
+ end
585
+
586
+ # Add a condition to the WHERE clause. See #filter for argument types.
587
+ #
588
+ # dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
589
+ # dataset.group(:a).having(:a).where(:b) # SELECT * FROM items WHERE b GROUP BY a HAVING a
590
+ def where(*cond, &block)
591
+ _filter(:where, *cond, &block)
592
+ end
593
+
594
+ # Add a simple common table expression (CTE) with the given name and a dataset that defines the CTE.
595
+ # A common table expression acts as an inline view for the query.
596
+ # Options:
597
+ # * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
598
+ # * :recursive - Specify that this is a recursive CTE
599
+ def with(name, dataset, opts={})
600
+ raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
601
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
602
+ end
603
+
604
+ # Add a recursive common table expression (CTE) with the given name, a dataset that
605
+ # defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
606
+ # of the CTE. Options:
607
+ # * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
608
+ # * :union_all - Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
609
+ def with_recursive(name, nonrecursive, recursive, opts={})
610
+ raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
611
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
612
+ end
613
+
614
+ # Returns a copy of the dataset with the static SQL used. This is useful if you want
615
+ # to keep the same row_proc/graph, but change the SQL used to custom SQL.
616
+ #
617
+ # dataset.with_sql('SELECT * FROM foo') # SELECT * FROM foo
618
+ def with_sql(sql, *args)
619
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
620
+ clone(:sql=>sql)
621
+ end
622
+
623
+ protected
624
+
625
+ # Return true if the dataset has a non-nil value for any key in opts.
626
+ def options_overlap(opts)
627
+ !(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
628
+ end
629
+
630
+ # Whether this dataset is a simple SELECT * FROM table.
631
+ def simple_select_all?
632
+ o = @opts.reject{|k,v| v.nil? || NON_SQL_OPTIONS.include?(k)}
633
+ o.length == 1 && (f = o[:from]) && f.length == 1 && f.first.is_a?(Symbol)
634
+ end
635
+
636
+ private
637
+
638
+ # Internal filter method so it works on either the having or where clauses.
639
+ def _filter(clause, *cond, &block)
640
+ cond = cond.first if cond.size == 1
641
+ if cond.respond_to?(:empty?) && cond.empty? && !block
642
+ clone
643
+ else
644
+ cond = filter_expr(cond, &block)
645
+ cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
646
+ clone(clause => cond)
647
+ end
648
+ end
649
+
650
+ # Add the dataset to the list of compounds
651
+ def compound_clone(type, dataset, opts)
652
+ ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
653
+ opts[:from_self] == false ? ds : ds.from_self(opts)
654
+ end
655
+
656
+ # SQL fragment based on the expr type. See #filter.
657
+ def filter_expr(expr = nil, &block)
658
+ expr = nil if expr == []
659
+ if expr && block
660
+ return SQL::BooleanExpression.new(:AND, filter_expr(expr), filter_expr(block))
661
+ elsif block
662
+ expr = block
663
+ end
664
+ case expr
665
+ when Hash
666
+ SQL::BooleanExpression.from_value_pairs(expr)
667
+ when Array
668
+ if (sexpr = expr.at(0)).is_a?(String)
669
+ SQL::PlaceholderLiteralString.new(sexpr, expr[1..-1], true)
670
+ elsif Sequel.condition_specifier?(expr)
671
+ SQL::BooleanExpression.from_value_pairs(expr)
672
+ else
673
+ SQL::BooleanExpression.new(:AND, *expr.map{|x| filter_expr(x)})
674
+ end
675
+ when Proc
676
+ filter_expr(Sequel.virtual_row(&expr))
677
+ when SQL::NumericExpression, SQL::StringExpression
678
+ raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
679
+ when Symbol, SQL::Expression
680
+ expr
681
+ when TrueClass, FalseClass
682
+ SQL::BooleanExpression.new(:NOOP, expr)
683
+ when String
684
+ LiteralString.new("(#{expr})")
685
+ else
686
+ raise(Error, 'Invalid filter argument')
687
+ end
688
+ end
689
+
690
+ # Inverts the given order by breaking it into a list of column references
691
+ # and inverting them.
692
+ #
693
+ # dataset.invert_order([:id.desc]]) #=> [:id]
694
+ # dataset.invert_order(:category, :price.desc]) #=>
695
+ # [:category.desc, :price]
696
+ def invert_order(order)
697
+ return nil unless order
698
+ new_order = []
699
+ order.map do |f|
700
+ case f
701
+ when SQL::OrderedExpression
702
+ f.invert
703
+ else
704
+ SQL::OrderedExpression.new(f)
705
+ end
706
+ end
707
+ end
708
+ end
709
+ end