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,996 @@
1
+ module Sequel
2
+ class Dataset
3
+ # ---------------------
4
+ # :section: User Methods relating to SQL Creation
5
+ # These are methods you can call to see what SQL will be generated by the dataset.
6
+ # ---------------------
7
+
8
+ # Formats a DELETE statement using the given options and dataset options.
9
+ #
10
+ # dataset.filter{|o| o.price >= 100}.delete_sql #=>
11
+ # "DELETE FROM items WHERE (price >= 100)"
12
+ def delete_sql
13
+ return static_sql(opts[:sql]) if opts[:sql]
14
+ check_modification_allowed!
15
+ clause_sql(:delete)
16
+ end
17
+
18
+ # Returns an EXISTS clause for the dataset as a LiteralString.
19
+ #
20
+ # DB.select(1).where(DB[:items].exists).sql
21
+ # #=> "SELECT 1 WHERE (EXISTS (SELECT * FROM items))"
22
+ def exists
23
+ LiteralString.new("EXISTS (#{select_sql})")
24
+ end
25
+
26
+ # Formats an INSERT statement using the given values. The API is a little
27
+ # complex, and best explained by example:
28
+ #
29
+ # # Default values
30
+ # DB[:items].insert_sql #=> 'INSERT INTO items DEFAULT VALUES'
31
+ # DB[:items].insert_sql({}) #=> 'INSERT INTO items DEFAULT VALUES'
32
+ # # Values without columns
33
+ # DB[:items].insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
34
+ # DB[:items].insert_sql([1,2,3]) #=> 'INSERT INTO items VALUES (1, 2, 3)'
35
+ # # Values with columns
36
+ # DB[:items].insert_sql([:a, :b], [1,2]) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
37
+ # DB[:items].insert_sql(:a => 1, :b => 2) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
38
+ # # Using a subselect
39
+ # DB[:items].insert_sql(DB[:old_items]) #=> 'INSERT INTO items SELECT * FROM old_items
40
+ # # Using a subselect with columns
41
+ # DB[:items].insert_sql([:a, :b], DB[:old_items]) #=> 'INSERT INTO items (a, b) SELECT * FROM old_items
42
+ def insert_sql(*values)
43
+ return static_sql(@opts[:sql]) if @opts[:sql]
44
+
45
+ check_modification_allowed!
46
+
47
+ columns = []
48
+
49
+ case values.size
50
+ when 0
51
+ return insert_sql({})
52
+ when 1
53
+ case vals = values.at(0)
54
+ when Hash
55
+ vals = @opts[:defaults].merge(vals) if @opts[:defaults]
56
+ vals = vals.merge(@opts[:overrides]) if @opts[:overrides]
57
+ values = []
58
+ vals.each do |k,v|
59
+ columns << k
60
+ values << v
61
+ end
62
+ when Dataset, Array, LiteralString
63
+ values = vals
64
+ else
65
+ if vals.respond_to?(:values) && (v = vals.values).is_a?(Hash)
66
+ return insert_sql(v)
67
+ end
68
+ end
69
+ when 2
70
+ if (v0 = values.at(0)).is_a?(Array) && ((v1 = values.at(1)).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
71
+ columns, values = v0, v1
72
+ raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
73
+ end
74
+ end
75
+
76
+ columns = columns.map{|k| literal(String === k ? k.to_sym : k)}
77
+ clone(:columns=>columns, :values=>values)._insert_sql
78
+ end
79
+
80
+ # Returns a literal representation of a value to be used as part
81
+ # of an SQL expression.
82
+ #
83
+ # dataset.literal("abc'def\\") #=> "'abc''def\\\\'"
84
+ # dataset.literal(:items__id) #=> "items.id"
85
+ # dataset.literal([1, 2, 3]) => "(1, 2, 3)"
86
+ # dataset.literal(DB[:items]) => "(SELECT * FROM items)"
87
+ # dataset.literal(:x + 1 > :y) => "((x + 1) > y)"
88
+ #
89
+ # If an unsupported object is given, an exception is raised.
90
+ def literal(v)
91
+ case v
92
+ when String
93
+ return v if v.is_a?(LiteralString)
94
+ v.is_a?(SQL::Blob) ? literal_blob(v) : literal_string(v)
95
+ when Symbol
96
+ literal_symbol(v)
97
+ when Integer
98
+ literal_integer(v)
99
+ when Hash
100
+ literal_hash(v)
101
+ when SQL::Expression
102
+ literal_expression(v)
103
+ when Float
104
+ literal_float(v)
105
+ when BigDecimal
106
+ literal_big_decimal(v)
107
+ when NilClass
108
+ literal_nil
109
+ when TrueClass
110
+ literal_true
111
+ when FalseClass
112
+ literal_false
113
+ when Array
114
+ literal_array(v)
115
+ when Time
116
+ literal_time(v)
117
+ when DateTime
118
+ literal_datetime(v)
119
+ when Date
120
+ literal_date(v)
121
+ when Dataset
122
+ literal_dataset(v)
123
+ else
124
+ literal_other(v)
125
+ end
126
+ end
127
+
128
+ # Returns an array of insert statements for inserting multiple records.
129
+ # This method is used by #multi_insert to format insert statements and
130
+ # expects a keys array and and an array of value arrays.
131
+ #
132
+ # This method should be overridden by descendants if the support
133
+ # inserting multiple records in a single SQL statement.
134
+ def multi_insert_sql(columns, values)
135
+ values.map{|r| insert_sql(columns, r)}
136
+ end
137
+
138
+ # Formats a SELECT statement
139
+ #
140
+ # dataset.select_sql # => "SELECT * FROM items"
141
+ def select_sql
142
+ return static_sql(@opts[:sql]) if @opts[:sql]
143
+ clause_sql(:select)
144
+ end
145
+
146
+ # Same as select_sql, not aliased directly to make subclassing simpler.
147
+ def sql
148
+ select_sql
149
+ end
150
+
151
+ # SQL query to truncate the table
152
+ def truncate_sql
153
+ if opts[:sql]
154
+ static_sql(opts[:sql])
155
+ else
156
+ check_modification_allowed!
157
+ raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where]
158
+ _truncate_sql(source_list(opts[:from]))
159
+ end
160
+ end
161
+
162
+ # Formats an UPDATE statement using the given values.
163
+ #
164
+ # dataset.update_sql(:price => 100, :category => 'software') #=>
165
+ # "UPDATE items SET price = 100, category = 'software'"
166
+ #
167
+ # Raises an error if the dataset is grouped or includes more
168
+ # than one table.
169
+ def update_sql(values = {})
170
+ return static_sql(opts[:sql]) if opts[:sql]
171
+ check_modification_allowed!
172
+ clone(:values=>values)._update_sql
173
+ end
174
+
175
+ # ---------------------
176
+ # :section: Internal Methods relating to SQL Creation
177
+ # These methods, while public, are not designed to be used directly by the end user.
178
+ # ---------------------
179
+
180
+ # Given a type (e.g. select) and an array of clauses,
181
+ # return an array of methods to call to build the SQL string.
182
+ def self.clause_methods(type, clauses)
183
+ clauses.map{|clause| :"#{type}_#{clause}_sql"}.freeze
184
+ end
185
+
186
+ AND_SEPARATOR = " AND ".freeze
187
+ BOOL_FALSE = "'f'".freeze
188
+ BOOL_TRUE = "'t'".freeze
189
+ COMMA_SEPARATOR = ', '.freeze
190
+ COLUMN_REF_RE1 = /\A([\w ]+)__([\w ]+)___([\w ]+)\z/.freeze
191
+ COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
192
+ COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
193
+ COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
194
+ COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
195
+ DATASET_ALIAS_BASE_NAME = 't'.freeze
196
+ FOR_UPDATE = ' FOR UPDATE'.freeze
197
+ IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
198
+ IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
199
+ N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
200
+ NULL = "NULL".freeze
201
+ QUALIFY_KEYS = [:select, :where, :having, :order, :group]
202
+ QUESTION_MARK = '?'.freeze
203
+ DELETE_CLAUSE_METHODS = clause_methods(:delete, %w'from where')
204
+ INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'into columns values')
205
+ SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct columns from join where group having compounds order limit lock')
206
+ UPDATE_CLAUSE_METHODS = clause_methods(:update, %w'table set where')
207
+ TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze
208
+ STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP #{TIMESTAMP_FORMAT}".freeze
209
+ TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
210
+ WILDCARD = LiteralString.new('*').freeze
211
+ SQL_WITH = "WITH ".freeze
212
+
213
+ # SQL fragment for the aliased expression
214
+ def aliased_expression_sql(ae)
215
+ as_sql(literal(ae.expression), ae.aliaz)
216
+ end
217
+
218
+ # SQL fragment for the SQL array.
219
+ def array_sql(a)
220
+ a.empty? ? '(NULL)' : "(#{expression_list(a)})"
221
+ end
222
+
223
+ # SQL fragment for BooleanConstants
224
+ def boolean_constant_sql(constant)
225
+ literal(constant)
226
+ end
227
+
228
+ # SQL fragment for specifying given CaseExpression.
229
+ def case_expression_sql(ce)
230
+ sql = '(CASE '
231
+ sql << "#{literal(ce.expression)} " if ce.expression
232
+ ce.conditions.collect{ |c,r|
233
+ sql << "WHEN #{literal(c)} THEN #{literal(r)} "
234
+ }
235
+ sql << "ELSE #{literal(ce.default)} END)"
236
+ end
237
+
238
+ # SQL fragment for the SQL CAST expression.
239
+ def cast_sql(expr, type)
240
+ "CAST(#{literal(expr)} AS #{db.cast_type_literal(type)})"
241
+ end
242
+
243
+ # SQL fragment for specifying all columns in a given table.
244
+ def column_all_sql(ca)
245
+ "#{quote_schema_table(ca.table)}.*"
246
+ end
247
+
248
+ # SQL fragment for complex expressions
249
+ def complex_expression_sql(op, args)
250
+ case op
251
+ when *IS_OPERATORS
252
+ r = args.at(1)
253
+ if r.nil? || supports_is_true?
254
+ raise(InvalidOperation, 'Invalid argument used for IS operator') unless v = IS_LITERALS[r]
255
+ "(#{literal(args.at(0))} #{op} #{v})"
256
+ elsif op == :IS
257
+ complex_expression_sql(:"=", args)
258
+ else
259
+ complex_expression_sql(:OR, [SQL::BooleanExpression.new(:"!=", *args), SQL::BooleanExpression.new(:IS, args.at(0), nil)])
260
+ end
261
+ when :IN, :"NOT IN"
262
+ cols = args.at(0)
263
+ vals = args.at(1)
264
+ col_array = true if cols.is_a?(Array) || cols.is_a?(SQL::SQLArray)
265
+ if vals.is_a?(Array) || vals.is_a?(SQL::SQLArray)
266
+ val_array = true
267
+ empty_val_array = vals.to_a == []
268
+ end
269
+ if col_array
270
+ if empty_val_array
271
+ if op == :IN
272
+ literal(SQL::BooleanExpression.from_value_pairs(cols.to_a.map{|x| [x, x]}, :AND, true))
273
+ else
274
+ literal(1=>1)
275
+ end
276
+ elsif !supports_multiple_column_in?
277
+ if val_array
278
+ expr = SQL::BooleanExpression.new(:OR, *vals.to_a.map{|vs| SQL::BooleanExpression.from_value_pairs(cols.to_a.zip(vs).map{|c, v| [c, v]})})
279
+ literal(op == :IN ? expr : ~expr)
280
+ else
281
+ old_vals = vals
282
+ vals = vals.to_a
283
+ val_cols = old_vals.columns
284
+ complex_expression_sql(op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
285
+ end
286
+ else
287
+ "(#{literal(cols)} #{op} #{literal(vals)})"
288
+ end
289
+ else
290
+ if empty_val_array
291
+ if op == :IN
292
+ literal(SQL::BooleanExpression.from_value_pairs([[cols, cols]], :AND, true))
293
+ else
294
+ literal(1=>1)
295
+ end
296
+ else
297
+ "(#{literal(cols)} #{op} #{literal(vals)})"
298
+ end
299
+ end
300
+ when *TWO_ARITY_OPERATORS
301
+ "(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
302
+ when *N_ARITY_OPERATORS
303
+ "(#{args.collect{|a| literal(a)}.join(" #{op} ")})"
304
+ when :NOT
305
+ "NOT #{literal(args.at(0))}"
306
+ when :NOOP
307
+ literal(args.at(0))
308
+ when :'B~'
309
+ "~#{literal(args.at(0))}"
310
+ else
311
+ raise(InvalidOperation, "invalid operator #{op}")
312
+ end
313
+ end
314
+
315
+ # SQL fragment for constants
316
+ def constant_sql(constant)
317
+ constant.to_s
318
+ end
319
+
320
+ # SQL fragment specifying an SQL function call
321
+ def function_sql(f)
322
+ args = f.args
323
+ "#{f.f}#{args.empty? ? '()' : literal(args)}"
324
+ end
325
+
326
+ # SQL fragment specifying a JOIN clause without ON or USING.
327
+ def join_clause_sql(jc)
328
+ table = jc.table
329
+ table_alias = jc.table_alias
330
+ table_alias = nil if table == table_alias
331
+ tref = table_ref(table)
332
+ " #{join_type_sql(jc.join_type)} #{table_alias ? as_sql(tref, table_alias) : tref}"
333
+ end
334
+
335
+ # SQL fragment specifying a JOIN clause with ON.
336
+ def join_on_clause_sql(jc)
337
+ "#{join_clause_sql(jc)} ON #{literal(filter_expr(jc.on))}"
338
+ end
339
+
340
+ # SQL fragment specifying a JOIN clause with USING.
341
+ def join_using_clause_sql(jc)
342
+ "#{join_clause_sql(jc)} USING (#{column_list(jc.using)})"
343
+ end
344
+
345
+ # SQL fragment for NegativeBooleanConstants
346
+ def negative_boolean_constant_sql(constant)
347
+ "NOT #{boolean_constant_sql(constant)}"
348
+ end
349
+
350
+ # SQL fragment for the ordered expression, used in the ORDER BY
351
+ # clause.
352
+ def ordered_expression_sql(oe)
353
+ "#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
354
+ end
355
+
356
+ # SQL fragment for a literal string with placeholders
357
+ def placeholder_literal_string_sql(pls)
358
+ args = pls.args
359
+ s = if args.is_a?(Hash)
360
+ re = /:(#{args.keys.map{|k| Regexp.escape(k.to_s)}.join('|')})\b/
361
+ pls.str.gsub(re){literal(args[$1.to_sym])}
362
+ else
363
+ i = -1
364
+ pls.str.gsub(QUESTION_MARK){literal(args.at(i+=1))}
365
+ end
366
+ s = "(#{s})" if pls.parens
367
+ s
368
+ end
369
+
370
+ # SQL fragment for the qualifed identifier, specifying
371
+ # a table and a column (or schema and table).
372
+ def qualified_identifier_sql(qcr)
373
+ [qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
374
+ end
375
+
376
+ # Adds quoting to identifiers (columns and tables). If identifiers are not
377
+ # being quoted, returns name as a string. If identifiers are being quoted
378
+ # quote the name with quoted_identifier.
379
+ def quote_identifier(name)
380
+ return name if name.is_a?(LiteralString)
381
+ name = name.value if name.is_a?(SQL::Identifier)
382
+ name = input_identifier(name)
383
+ name = quoted_identifier(name) if quote_identifiers?
384
+ name
385
+ end
386
+
387
+ # Separates the schema from the table and returns a string with them
388
+ # quoted (if quoting identifiers)
389
+ def quote_schema_table(table)
390
+ schema, table = schema_and_table(table)
391
+ "#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
392
+ end
393
+
394
+ # This method quotes the given name with the SQL standard double quote.
395
+ # should be overridden by subclasses to provide quoting not matching the
396
+ # SQL standard, such as backtick (used by MySQL and SQLite).
397
+ def quoted_identifier(name)
398
+ "\"#{name.to_s.gsub('"', '""')}\""
399
+ end
400
+
401
+ # Split the schema information from the table
402
+ def schema_and_table(table_name)
403
+ sch = db.default_schema if db
404
+ case table_name
405
+ when Symbol
406
+ s, t, a = split_symbol(table_name)
407
+ [s||sch, t]
408
+ when SQL::QualifiedIdentifier
409
+ [table_name.table, table_name.column]
410
+ when SQL::Identifier
411
+ [sch, table_name.value]
412
+ when String
413
+ [sch, table_name]
414
+ else
415
+ raise Error, 'table_name should be a Symbol, SQL::QualifiedIdentifier, SQL::Identifier, or String'
416
+ end
417
+ end
418
+
419
+ # SQL fragment for specifying subscripts (SQL arrays)
420
+ def subscript_sql(s)
421
+ "#{literal(s.f)}[#{expression_list(s.sub)}]"
422
+ end
423
+
424
+ # The SQL fragment for the given window's options.
425
+ def window_sql(opts)
426
+ raise(Error, 'This dataset does not support window functions') unless supports_window_functions?
427
+ window = literal(opts[:window]) if opts[:window]
428
+ partition = "PARTITION BY #{expression_list(Array(opts[:partition]))}" if opts[:partition]
429
+ order = "ORDER BY #{expression_list(Array(opts[:order]))}" if opts[:order]
430
+ frame = case opts[:frame]
431
+ when nil
432
+ nil
433
+ when :all
434
+ "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
435
+ when :rows
436
+ "ROWS UNBOUNDED PRECEDING"
437
+ else
438
+ raise Error, "invalid window frame clause, should be :all, :rows, or nil"
439
+ end
440
+ "(#{[window, partition, order, frame].compact.join(' ')})"
441
+ end
442
+
443
+ # The SQL fragment for the given window function's function and window.
444
+ def window_function_sql(function, window)
445
+ "#{literal(function)} OVER #{literal(window)}"
446
+ end
447
+
448
+ protected
449
+
450
+ # Formats in INSERT statement using the stored columns and values.
451
+ def _insert_sql
452
+ clause_sql(:insert)
453
+ end
454
+
455
+ # Formats an UPDATE statement using the stored values.
456
+ def _update_sql
457
+ clause_sql(:update)
458
+ end
459
+
460
+ # Return a from_self dataset if an order or limit is specified, so it works as expected
461
+ # with UNION, EXCEPT, and INTERSECT clauses.
462
+ def compound_from_self
463
+ (@opts[:limit] || @opts[:order]) ? from_self : self
464
+ end
465
+
466
+ private
467
+
468
+ # Formats the truncate statement. Assumes the table given has already been
469
+ # literalized.
470
+ def _truncate_sql(table)
471
+ "TRUNCATE TABLE #{table}"
472
+ end
473
+
474
+ # Returns an appropriate symbol for the alias represented by s.
475
+ def alias_alias_symbol(s)
476
+ case s
477
+ when Symbol
478
+ s
479
+ when String
480
+ s.to_sym
481
+ when SQL::Identifier
482
+ s.value.to_s.to_sym
483
+ else
484
+ raise Error, "Invalid alias for alias_alias_symbol: #{s.inspect}"
485
+ end
486
+ end
487
+
488
+ # Returns an appropriate alias symbol for the given object, which can be
489
+ # a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or
490
+ # SQL::AliasedExpression.
491
+ def alias_symbol(sym)
492
+ case sym
493
+ when Symbol
494
+ s, t, a = split_symbol(sym)
495
+ a || s ? (a || t).to_sym : sym
496
+ when String
497
+ sym.to_sym
498
+ when SQL::Identifier
499
+ sym.value.to_s.to_sym
500
+ when SQL::QualifiedIdentifier
501
+ alias_symbol(sym.column)
502
+ when SQL::AliasedExpression
503
+ alias_alias_symbol(sym.aliaz)
504
+ else
505
+ raise Error, "Invalid alias for alias_symbol: #{sym.inspect}"
506
+ end
507
+ end
508
+
509
+ # Clone of this dataset usable in aggregate operations. Does
510
+ # a from_self if dataset contains any parameters that would
511
+ # affect normal aggregation, or just removes an existing
512
+ # order if not.
513
+ def aggregate_dataset
514
+ options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered
515
+ end
516
+
517
+ # Do a simple join of the arguments (which should be strings or symbols) separated by commas
518
+ def argument_list(args)
519
+ args.join(COMMA_SEPARATOR)
520
+ end
521
+
522
+ # SQL fragment for specifying an alias. expression should already be literalized.
523
+ def as_sql(expression, aliaz)
524
+ "#{expression} AS #{quote_identifier(aliaz)}"
525
+ end
526
+
527
+ # Raise an InvalidOperation exception if deletion is not allowed
528
+ # for this dataset
529
+ def check_modification_allowed!
530
+ raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
531
+ raise(InvalidOperation, "Joined datasets cannot be modified") if !supports_modifying_joins? && joined_dataset?
532
+ end
533
+
534
+ # Prepare an SQL statement by calling all clause methods for the given statement type.
535
+ def clause_sql(type)
536
+ sql = type.to_s.upcase
537
+ send("#{type}_clause_methods").each{|x| send(x, sql)}
538
+ sql
539
+ end
540
+
541
+ # Converts an array of column names into a comma seperated string of
542
+ # column names. If the array is empty, a wildcard (*) is returned.
543
+ def column_list(columns)
544
+ (columns.nil? || columns.empty?) ? WILDCARD : expression_list(columns)
545
+ end
546
+
547
+ # The alias to use for datasets, takes a number to make sure the name is unique.
548
+ def dataset_alias(number)
549
+ :"#{DATASET_ALIAS_BASE_NAME}#{number}"
550
+ end
551
+
552
+ # The order of methods to call to build the DELETE SQL statement
553
+ def delete_clause_methods
554
+ DELETE_CLAUSE_METHODS
555
+ end
556
+
557
+ # Converts an array of expressions into a comma separated string of
558
+ # expressions.
559
+ def expression_list(columns)
560
+ columns.map{|i| literal(i)}.join(COMMA_SEPARATOR)
561
+ end
562
+
563
+ # The strftime format to use when literalizing the time.
564
+ def default_timestamp_format
565
+ requires_sql_standard_datetimes? ? STANDARD_TIMESTAMP_FORMAT : TIMESTAMP_FORMAT
566
+ end
567
+
568
+ # Format the timestamp based on the default_timestamp_format, with a couple
569
+ # of modifiers. First, allow %N to be used for fractions seconds (if the
570
+ # database supports them), and override %z to always use a numeric offset
571
+ # of hours and minutes.
572
+ def format_timestamp(v)
573
+ v2 = Sequel.application_to_database_timestamp(v)
574
+ fmt = default_timestamp_format.gsub(/%[Nz]/) do |m|
575
+ if m == '%N'
576
+ format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*86400000000 : v.usec) if supports_timestamp_usecs?
577
+ else
578
+ if supports_timestamp_timezones?
579
+ # Would like to just use %z format, but it doesn't appear to work on Windows
580
+ # Instead, the offset fragment is constructed manually
581
+ minutes = (v2.is_a?(DateTime) ? v2.offset * 1440 : v2.utc_offset/60).to_i
582
+ format_timestamp_offset(*minutes.divmod(60))
583
+ end
584
+ end
585
+ end
586
+ v2.strftime(fmt)
587
+ end
588
+
589
+ # Return the SQL timestamp fragment to use for the timezone offset.
590
+ def format_timestamp_offset(hour, minute)
591
+ sprintf("%+03i%02i", hour, minute)
592
+ end
593
+
594
+ # Return the SQL timestamp fragment to use for the fractional time part.
595
+ # Should start with the decimal point. Uses 6 decimal places by default.
596
+ def format_timestamp_usec(usec)
597
+ sprintf(".%06d", usec)
598
+ end
599
+
600
+ # SQL fragment specifying a list of identifiers
601
+ # SQL fragment specifying a list of identifiers
602
+ def identifier_list(columns)
603
+ columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
604
+ end
605
+
606
+ # Modify the identifier returned from the database based on the
607
+ # identifier_output_method.
608
+ def input_identifier(v)
609
+ (i = identifier_input_method) ? v.to_s.send(i) : v.to_s
610
+ end
611
+
612
+ # SQL fragment specifying the table to insert INTO
613
+ def insert_into_sql(sql)
614
+ sql << " INTO #{source_list(@opts[:from])}"
615
+ end
616
+
617
+ # The order of methods to call to build the INSERT SQL statement
618
+ def insert_clause_methods
619
+ INSERT_CLAUSE_METHODS
620
+ end
621
+
622
+ # SQL fragment specifying the columns to insert into
623
+ def insert_columns_sql(sql)
624
+ columns = opts[:columns]
625
+ sql << " (#{columns.join(COMMA_SEPARATOR)})" if columns && !columns.empty?
626
+ end
627
+
628
+ # SQL fragment specifying the values to insert.
629
+ def insert_values_sql(sql)
630
+ case values = opts[:values]
631
+ when Array
632
+ sql << (values.empty? ? " DEFAULT VALUES" : " VALUES #{literal(values)}")
633
+ when Dataset
634
+ sql << " #{subselect_sql(values)}"
635
+ when LiteralString
636
+ sql << " #{values}"
637
+ else
638
+ raise Error, "Unsupported INSERT values type, should be an Array or Dataset: #{values.inspect}"
639
+ end
640
+ end
641
+
642
+ # SQL fragment specifying a JOIN type, converts underscores to
643
+ # spaces and upcases.
644
+ def join_type_sql(join_type)
645
+ "#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
646
+ end
647
+
648
+ # Whether this dataset is a joined dataset
649
+ def joined_dataset?
650
+ (opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join]
651
+ end
652
+
653
+ # SQL fragment for Array. Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
654
+ def literal_array(v)
655
+ Sequel.condition_specifier?(v) ? literal_expression(SQL::BooleanExpression.from_value_pairs(v)) : array_sql(v)
656
+ end
657
+
658
+ # SQL fragment for BigDecimal
659
+ def literal_big_decimal(v)
660
+ d = v.to_s("F")
661
+ v.nan? || v.infinite? ? "'#{d}'" : d
662
+ end
663
+
664
+ # SQL fragment for SQL::Blob
665
+ def literal_blob(v)
666
+ literal_string(v)
667
+ end
668
+
669
+ # SQL fragment for Dataset. Does a subselect inside parantheses.
670
+ def literal_dataset(v)
671
+ "(#{subselect_sql(v)})"
672
+ end
673
+
674
+ # SQL fragment for Date, using the ISO8601 format.
675
+ def literal_date(v)
676
+ v.strftime("#{'DATE ' if requires_sql_standard_datetimes?}'%Y-%m-%d'")
677
+ end
678
+
679
+ # SQL fragment for DateTime
680
+ def literal_datetime(v)
681
+ format_timestamp(v)
682
+ end
683
+
684
+ # SQL fragment for SQL::Expression, result depends on the specific type of expression.
685
+ def literal_expression(v)
686
+ v.to_s(self)
687
+ end
688
+
689
+ # SQL fragment for false
690
+ def literal_false
691
+ BOOL_FALSE
692
+ end
693
+
694
+ # SQL fragment for Float
695
+ def literal_float(v)
696
+ v.to_s
697
+ end
698
+
699
+ # SQL fragment for Hash, treated as an expression
700
+ def literal_hash(v)
701
+ literal_expression(SQL::BooleanExpression.from_value_pairs(v))
702
+ end
703
+
704
+ # SQL fragment for Integer
705
+ def literal_integer(v)
706
+ v.to_s
707
+ end
708
+
709
+ # SQL fragment for nil
710
+ def literal_nil
711
+ NULL
712
+ end
713
+
714
+ # SQL fragment for a type of object not handled by Dataset#literal.
715
+ # Calls sql_literal if object responds to it, otherwise raises an error.
716
+ # Classes implementing sql_literal should call a class-specific method on the dataset
717
+ # provided and should add that method to Sequel::Dataset, allowing for adapters
718
+ # to provide customized literalizations.
719
+ # If a database specific type is allowed, this should be overriden in a subclass.
720
+ def literal_other(v)
721
+ if v.respond_to?(:sql_literal)
722
+ v.sql_literal(self)
723
+ else
724
+ raise Error, "can't express #{v.inspect} as a SQL literal"
725
+ end
726
+ end
727
+
728
+ # SQL fragment for String. Doubles \ and ' by default.
729
+ def literal_string(v)
730
+ "'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
731
+ end
732
+
733
+ # Converts a symbol into a column name. This method supports underscore
734
+ # notation in order to express qualified (two underscores) and aliased
735
+ # (three underscores) columns:
736
+ #
737
+ # dataset.literal(:abc) #=> "abc"
738
+ # dataset.literal(:abc___a) #=> "abc AS a"
739
+ # dataset.literal(:items__abc) #=> "items.abc"
740
+ # dataset.literal(:items__abc___a) #=> "items.abc AS a"
741
+ def literal_symbol(v)
742
+ c_table, column, c_alias = split_symbol(v)
743
+ qc = "#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}"
744
+ c_alias ? as_sql(qc, c_alias) : qc
745
+ end
746
+
747
+ # SQL fragment for Time
748
+ def literal_time(v)
749
+ format_timestamp(v)
750
+ end
751
+
752
+ # SQL fragment for true
753
+ def literal_true
754
+ BOOL_TRUE
755
+ end
756
+
757
+ # Returns a qualified column name (including a table name) if the column
758
+ # name isn't already qualified.
759
+ def qualified_column_name(column, table)
760
+ if Symbol === column
761
+ c_table, column, c_alias = split_symbol(column)
762
+ unless c_table
763
+ case table
764
+ when Symbol
765
+ schema, table, t_alias = split_symbol(table)
766
+ t_alias ||= Sequel::SQL::QualifiedIdentifier.new(schema, table) if schema
767
+ when Sequel::SQL::AliasedExpression
768
+ t_alias = table.aliaz
769
+ end
770
+ c_table = t_alias || table
771
+ end
772
+ ::Sequel::SQL::QualifiedIdentifier.new(c_table, column)
773
+ else
774
+ column
775
+ end
776
+ end
777
+
778
+ # Qualify the given expression e to the given table.
779
+ def qualified_expression(e, table)
780
+ case e
781
+ when Symbol
782
+ t, column, aliaz = split_symbol(e)
783
+ if t
784
+ e
785
+ elsif aliaz
786
+ SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(table, SQL::Identifier.new(column)), aliaz)
787
+ else
788
+ SQL::QualifiedIdentifier.new(table, e)
789
+ end
790
+ when Array
791
+ e.map{|a| qualified_expression(a, table)}
792
+ when Hash
793
+ h = {}
794
+ e.each{|k,v| h[qualified_expression(k, table)] = qualified_expression(v, table)}
795
+ h
796
+ when SQL::Identifier
797
+ SQL::QualifiedIdentifier.new(table, e)
798
+ when SQL::OrderedExpression
799
+ SQL::OrderedExpression.new(qualified_expression(e.expression, table), e.descending)
800
+ when SQL::AliasedExpression
801
+ SQL::AliasedExpression.new(qualified_expression(e.expression, table), e.aliaz)
802
+ when SQL::CaseExpression
803
+ SQL::CaseExpression.new(qualified_expression(e.conditions, table), qualified_expression(e.default, table), qualified_expression(e.expression, table))
804
+ when SQL::Cast
805
+ SQL::Cast.new(qualified_expression(e.expr, table), e.type)
806
+ when SQL::Function
807
+ SQL::Function.new(e.f, *qualified_expression(e.args, table))
808
+ when SQL::ComplexExpression
809
+ SQL::ComplexExpression.new(e.op, *qualified_expression(e.args, table))
810
+ when SQL::SQLArray
811
+ SQL::SQLArray.new(qualified_expression(e.array, table))
812
+ when SQL::Subscript
813
+ SQL::Subscript.new(qualified_expression(e.f, table), qualified_expression(e.sub, table))
814
+ when SQL::WindowFunction
815
+ SQL::WindowFunction.new(qualified_expression(e.function, table), qualified_expression(e.window, table))
816
+ when SQL::Window
817
+ o = e.opts.dup
818
+ o[:partition] = qualified_expression(o[:partition], table) if o[:partition]
819
+ o[:order] = qualified_expression(o[:order], table) if o[:order]
820
+ SQL::Window.new(o)
821
+ when SQL::PlaceholderLiteralString
822
+ args = if e.args.is_a?(Hash)
823
+ h = {}
824
+ e.args.each{|k,v| h[k] = qualified_expression(v, table)}
825
+ h
826
+ else
827
+ qualified_expression(e.args, table)
828
+ end
829
+ SQL::PlaceholderLiteralString.new(e.str, args, e.parens)
830
+ else
831
+ e
832
+ end
833
+ end
834
+
835
+ # The order of methods to call to build the SELECT SQL statement
836
+ def select_clause_methods
837
+ SELECT_CLAUSE_METHODS
838
+ end
839
+
840
+ # Modify the sql to add the columns selected
841
+ def select_columns_sql(sql)
842
+ sql << " #{column_list(@opts[:select])}"
843
+ end
844
+
845
+ # Modify the sql to add the DISTINCT modifier
846
+ def select_distinct_sql(sql)
847
+ if distinct = @opts[:distinct]
848
+ sql << " DISTINCT#{" ON (#{expression_list(distinct)})" unless distinct.empty?}"
849
+ end
850
+ end
851
+
852
+ # Modify the sql to add a dataset to the via an EXCEPT, INTERSECT, or UNION clause.
853
+ # This uses a subselect for the compound datasets used, because using parantheses doesn't
854
+ # work on all databases. I consider this an ugly hack, but can't I think of a better default.
855
+ def select_compounds_sql(sql)
856
+ return unless @opts[:compounds]
857
+ @opts[:compounds].each do |type, dataset, all|
858
+ compound_sql = subselect_sql(dataset)
859
+ sql << " #{type.to_s.upcase}#{' ALL' if all} #{compound_sql}"
860
+ end
861
+ end
862
+
863
+ # Modify the sql to add the list of tables to select FROM
864
+ def select_from_sql(sql)
865
+ sql << " FROM #{source_list(@opts[:from])}" if @opts[:from]
866
+ end
867
+ alias delete_from_sql select_from_sql
868
+
869
+ # Modify the sql to add the expressions to GROUP BY
870
+ def select_group_sql(sql)
871
+ sql << " GROUP BY #{expression_list(@opts[:group])}" if @opts[:group]
872
+ end
873
+
874
+ # Modify the sql to add the filter criteria in the HAVING clause
875
+ def select_having_sql(sql)
876
+ sql << " HAVING #{literal(@opts[:having])}" if @opts[:having]
877
+ end
878
+
879
+ # Modify the sql to add the list of tables to JOIN to
880
+ def select_join_sql(sql)
881
+ @opts[:join].each{|j| sql << literal(j)} if @opts[:join]
882
+ end
883
+
884
+ # Modify the sql to limit the number of rows returned and offset
885
+ def select_limit_sql(sql)
886
+ sql << " LIMIT #{literal(@opts[:limit])}" if @opts[:limit]
887
+ sql << " OFFSET #{literal(@opts[:offset])}" if @opts[:offset]
888
+ end
889
+
890
+ # Modify the sql to support the different types of locking modes.
891
+ def select_lock_sql(sql)
892
+ case @opts[:lock]
893
+ when :update
894
+ sql << FOR_UPDATE
895
+ when String
896
+ sql << " #{@opts[:lock]}"
897
+ end
898
+ end
899
+
900
+ # Modify the sql to add the expressions to ORDER BY
901
+ def select_order_sql(sql)
902
+ sql << " ORDER BY #{expression_list(@opts[:order])}" if @opts[:order]
903
+ end
904
+ alias delete_order_sql select_order_sql
905
+ alias update_order_sql select_order_sql
906
+
907
+ # Modify the sql to add the filter criteria in the WHERE clause
908
+ def select_where_sql(sql)
909
+ sql << " WHERE #{literal(@opts[:where])}" if @opts[:where]
910
+ end
911
+ alias delete_where_sql select_where_sql
912
+ alias update_where_sql select_where_sql
913
+
914
+ # SQL Fragment specifying the WITH clause
915
+ def select_with_sql(sql)
916
+ ws = opts[:with]
917
+ return if !ws || ws.empty?
918
+ sql.replace("#{select_with_sql_base}#{ws.map{|w| "#{quote_identifier(w[:name])}#{"(#{argument_list(w[:args])})" if w[:args]} AS #{literal_dataset(w[:dataset])}"}.join(COMMA_SEPARATOR)} #{sql}")
919
+ end
920
+
921
+ # The base keyword to use for the SQL WITH clause
922
+ def select_with_sql_base
923
+ SQL_WITH
924
+ end
925
+
926
+ # Converts an array of source names into into a comma separated list.
927
+ def source_list(source)
928
+ raise(Error, 'No source specified for query') if source.nil? || source.empty?
929
+ source.map{|s| table_ref(s)}.join(COMMA_SEPARATOR)
930
+ end
931
+
932
+ # Splits the symbol into three parts. Each part will
933
+ # either be a string or nil.
934
+ #
935
+ # For columns, these parts are the table, column, and alias.
936
+ # For tables, these parts are the schema, table, and alias.
937
+ def split_symbol(sym)
938
+ s = sym.to_s
939
+ if m = COLUMN_REF_RE1.match(s)
940
+ m[1..3]
941
+ elsif m = COLUMN_REF_RE2.match(s)
942
+ [nil, m[1], m[2]]
943
+ elsif m = COLUMN_REF_RE3.match(s)
944
+ [m[1], m[2], nil]
945
+ else
946
+ [nil, s, nil]
947
+ end
948
+ end
949
+
950
+ # SQL to use if this dataset uses static SQL. Since static SQL
951
+ # can be a PlaceholderLiteralString in addition to a String,
952
+ # we literalize nonstrings.
953
+ def static_sql(sql)
954
+ sql.is_a?(String) ? sql : literal(sql)
955
+ end
956
+
957
+ # SQL fragment for a subselect using the given database's SQL.
958
+ def subselect_sql(ds)
959
+ ds.sql
960
+ end
961
+
962
+ # SQL fragment specifying a table name.
963
+ def table_ref(t)
964
+ t.is_a?(String) ? quote_identifier(t) : literal(t)
965
+ end
966
+
967
+ # The order of methods to call to build the UPDATE SQL statement
968
+ def update_clause_methods
969
+ UPDATE_CLAUSE_METHODS
970
+ end
971
+
972
+ # SQL fragment specifying the tables from with to delete.
973
+ # Includes join table if modifying joins is allowed.
974
+ def update_table_sql(sql)
975
+ sql << " #{source_list(@opts[:from])}"
976
+ select_join_sql(sql) if supports_modifying_joins?
977
+ end
978
+
979
+ # The SQL fragment specifying the columns and values to SET.
980
+ def update_set_sql(sql)
981
+ values = opts[:values]
982
+ set = if values.is_a?(Hash)
983
+ values = opts[:defaults].merge(values) if opts[:defaults]
984
+ values = values.merge(opts[:overrides]) if opts[:overrides]
985
+ # get values from hash
986
+ values.map do |k, v|
987
+ "#{k.is_a?(String) && !k.is_a?(LiteralString) ? quote_identifier(k) : literal(k)} = #{literal(v)}"
988
+ end.join(COMMA_SEPARATOR)
989
+ else
990
+ # copy values verbatim
991
+ values
992
+ end
993
+ sql << " SET #{set}"
994
+ end
995
+ end
996
+ end