viking-sequel 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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