sequel 3.21.0 → 3.31.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 (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. metadata +115 -73
@@ -7,16 +7,9 @@ module Sequel
7
7
  AUTO_VACUUM = [:none, :full, :incremental].freeze
8
8
  PRIMARY_KEY_INDEX_RE = /\Asqlite_autoindex_/.freeze
9
9
  SYNCHRONOUS = [:off, :normal, :full].freeze
10
- TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
10
+ TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'".freeze
11
11
  TEMP_STORE = [:default, :file, :memory].freeze
12
-
13
- # Run all alter_table commands in a transaction. This is technically only
14
- # needed for drop column.
15
- def alter_table(name, generator=nil, &block)
16
- remove_cached_schema(name)
17
- generator ||= Schema::AlterTableGenerator.new(self, &block)
18
- transaction{generator.operations.each{|op| alter_table_sql_list(name, [op]).flatten.each{|sql| execute_ddl(sql)}}}
19
- end
12
+ VIEWS_FILTER = "type = 'view'".freeze
20
13
 
21
14
  # A symbol signifying the value of the auto_vacuum PRAGMA.
22
15
  def auto_vacuum
@@ -30,7 +23,20 @@ module Sequel
30
23
  value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
31
24
  pragma_set(:auto_vacuum, value)
32
25
  end
26
+
27
+ # Boolean signifying the value of the case_sensitive_likePRAGMA, or nil
28
+ # if not using SQLite 3.2.3+.
29
+ def case_sensitive_like
30
+ pragma_get(:case_sensitive_like).to_i == 1 if sqlite_version >= 30203
31
+ end
33
32
 
33
+ # Set the case_sensitive_like PRAGMA using the given boolean value, if using
34
+ # SQLite 3.2.3+. If not using 3.2.3+, no error is raised. See pragma_set.
35
+ # Consider using the :case_sensitive_like Database option instead.
36
+ def case_sensitive_like=(value)
37
+ pragma_set(:case_sensitive_like, !!value ? 'on' : 'off') if sqlite_version >= 30203
38
+ end
39
+
34
40
  # SQLite uses the :sqlite database type.
35
41
  def database_type
36
42
  :sqlite
@@ -96,11 +102,29 @@ module Sequel
96
102
  end
97
103
  end
98
104
 
105
+ # SQLite supports CREATE TABLE IF NOT EXISTS syntax since 3.3.0.
106
+ def supports_create_table_if_not_exists?
107
+ sqlite_version >= 30300
108
+ end
109
+
99
110
  # SQLite 3.6.8+ supports savepoints.
100
111
  def supports_savepoints?
101
112
  sqlite_version >= 30608
102
113
  end
103
114
 
115
+ # Override the default setting for whether to use timezones in timestamps.
116
+ # For backwards compatibility, it is set to +true+ by default.
117
+ # Anyone wanting to use SQLite's datetime functions should set it to +false+
118
+ # using this method. It's possible that the default will change in a future version,
119
+ # so anyone relying on timezones in timestamps should set this to +true+.
120
+ attr_writer :use_timestamp_timezones
121
+
122
+ # SQLite supports timezones in timestamps, since it just stores them as strings,
123
+ # but it breaks the usage of SQLite's datetime functions.
124
+ def use_timestamp_timezones?
125
+ defined?(@use_timestamp_timezones) ? @use_timestamp_timezones : (@use_timestamp_timezones = true)
126
+ end
127
+
104
128
  # A symbol signifying the value of the synchronous PRAGMA.
105
129
  def synchronous
106
130
  SYNCHRONOUS[pragma_get(:synchronous).to_i]
@@ -118,8 +142,7 @@ module Sequel
118
142
  # Options:
119
143
  # * :server - Set the server to use.
120
144
  def tables(opts={})
121
- m = output_identifier_meth
122
- metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(TABLES_FILTER).map{|r| m.call(r[:name])}
145
+ tables_and_views(TABLES_FILTER, opts)
123
146
  end
124
147
 
125
148
  # A symbol signifying the value of the temp_store PRAGMA.
@@ -134,8 +157,32 @@ module Sequel
134
157
  pragma_set(:temp_store, value)
135
158
  end
136
159
 
160
+ # Array of symbols specifying the view names in the current database.
161
+ #
162
+ # Options:
163
+ # * :server - Set the server to use.
164
+ def views(opts={})
165
+ tables_and_views(VIEWS_FILTER, opts)
166
+ end
167
+
137
168
  private
138
169
 
170
+ # Run all alter_table commands in a transaction. This is technically only
171
+ # needed for drop column.
172
+ def apply_alter_table(table, ops)
173
+ transaction do
174
+ if ops.length > 1 && ops.all?{|op| op[:op] == :add_constraint}
175
+ # If you are just doing constraints, apply all of them at the same time,
176
+ # as otherwise all but the last one get lost.
177
+ alter_table_sql_list(table, [{:op=>:add_constraints, :ops=>ops}]).flatten.each{|sql| execute_ddl(sql)}
178
+ else
179
+ # Run each operation separately, as later operations may depend on the
180
+ # results of earlier operations.
181
+ ops.each{|op| alter_table_sql_list(table, [op]).flatten.each{|sql| execute_ddl(sql)}}
182
+ end
183
+ end
184
+ end
185
+
139
186
  # SQLite supports limited table modification. You can add a column
140
187
  # or an index. Dropping columns is supported by copying the table into
141
188
  # a temporary table, dropping the table, and creating a new table without
@@ -162,11 +209,26 @@ module Sequel
162
209
  duplicate_table(table){|columns| columns.each{|s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s}}
163
210
  when :set_column_type
164
211
  duplicate_table(table){|columns| columns.each{|s| s[:type] = op[:type] if s[:name].to_s == op[:name].to_s}}
212
+ when :drop_constraint
213
+ case op[:type]
214
+ when :primary_key
215
+ duplicate_table(table){|columns| columns.each{|s| s[:primary_key] = nil}}
216
+ when :foreign_key
217
+ duplicate_table(table){|columns| columns.each{|s| s[:table] = nil}}
218
+ when :unique
219
+ duplicate_table(table)
220
+ else
221
+ raise Error, "Unsupported :type option for drop_constraint: #{op[:type].inspect}"
222
+ end
223
+ when :add_constraint
224
+ duplicate_table(table, :constraints=>[op])
225
+ when :add_constraints
226
+ duplicate_table(table, :constraints=>op[:ops])
165
227
  else
166
- raise Error, "Unsupported ALTER TABLE operation"
228
+ raise Error, "Unsupported ALTER TABLE operation: #{op[:op].inspect}"
167
229
  end
168
230
  end
169
-
231
+
170
232
  # The array of column symbols in the table, except for ones given in opts[:except]
171
233
  def backup_table_name(table, opts={})
172
234
  table = table.gsub('`', '')
@@ -176,17 +238,14 @@ module Sequel
176
238
  end
177
239
  end
178
240
 
179
- # Allow use without a generator, needed for the alter table hackery that Sequel allows.
180
- def column_list_sql(generator)
181
- generator.is_a?(Schema::Generator) ? super : generator.map{|c| column_definition_sql(c)}.join(', ')
182
- end
183
-
184
241
  # Array of PRAGMA SQL statements based on the Database options that should be applied to
185
242
  # new connections.
186
243
  def connection_pragmas
187
244
  ps = []
188
245
  v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
189
246
  ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
247
+ v = typecast_value_boolean(opts.fetch(:case_sensitive_like, 1))
248
+ ps << "PRAGMA case_sensitive_like = #{v ? 1 : 0}"
190
249
  [[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
191
250
  if v = opts[prag]
192
251
  raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
@@ -208,15 +267,20 @@ module Sequel
208
267
  cols.reject!{|c| nono.include? c[:name] }
209
268
  end
210
269
 
211
- if foreign_keys
212
- metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
213
- c = cols.find {|co| co[:name] == row[:from] } or next
214
- c[:table] = row[:table]
215
- c[:key] = row[:to]
216
- c[:on_update] = on_delete_sql_to_sym(row[:on_update])
217
- c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
218
- # is there any way to get deferrable status?
270
+ begin
271
+ if foreign_keys
272
+ metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
273
+ c = cols.find {|co| co[:name] == row[:from] } or next
274
+ c[:table] = row[:table]
275
+ c[:key] = row[:to]
276
+ c[:on_update] = on_delete_sql_to_sym(row[:on_update])
277
+ c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
278
+ # is there any way to get deferrable status?
279
+ end
219
280
  end
281
+ rescue Sequel::DatabaseError
282
+ # Doesn't work correctly on some versions of JDBC SQLite,
283
+ # giving a "query does not return ResultSet" error.
220
284
  end
221
285
  cols
222
286
  end
@@ -231,17 +295,26 @@ module Sequel
231
295
  opts[:old_columns_proc].call(old_columns) if opts[:old_columns_proc]
232
296
 
233
297
  yield def_columns if block_given?
234
- def_columns_str = column_list_sql(def_columns)
298
+
299
+ constraints = (opts[:constraints] || []).dup
300
+ pks = []
301
+ def_columns.each{|c| pks << c[:name] if c[:primary_key]}
302
+ if pks.length > 1
303
+ constraints << {:type=>:primary_key, :columns=>pks}
304
+ def_columns.each{|c| c[:primary_key] = false if c[:primary_key]}
305
+ end
306
+
307
+ def_columns_str = (def_columns.map{|c| column_definition_sql(c)} + constraints.map{|c| constraint_definition_sql(c)}).join(', ')
235
308
  new_columns = old_columns.dup
236
309
  opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
237
310
 
238
311
  qt = quote_schema_table(table)
239
312
  bt = quote_identifier(backup_table_name(qt))
240
313
  a = [
241
- "CREATE TABLE #{bt}(#{def_columns_str})",
242
- "INSERT INTO #{bt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{qt}",
243
- "DROP TABLE #{qt}",
244
- "ALTER TABLE #{bt} RENAME TO #{qt}"
314
+ "ALTER TABLE #{qt} RENAME TO #{bt}",
315
+ "CREATE TABLE #{qt}(#{def_columns_str})",
316
+ "INSERT INTO #{qt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{bt}",
317
+ "DROP TABLE #{bt}"
245
318
  ]
246
319
  indexes(table).each do |name, h|
247
320
  if (h[:columns].map{|x| x.to_s} - new_columns).empty?
@@ -280,7 +353,7 @@ module Sequel
280
353
 
281
354
  # Parse the output of the table_info pragma
282
355
  def parse_pragma(table_name, opts)
283
- metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth.call(table_name)).map do |row|
356
+ metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth(opts[:dataset]).call(table_name)).map do |row|
284
357
  row.delete(:cid)
285
358
  row[:allow_null] = row.delete(:notnull).to_i == 0
286
359
  row[:default] = row.delete(:dflt_value)
@@ -300,12 +373,18 @@ module Sequel
300
373
  # SQLite supports schema parsing using the table_info PRAGMA, so
301
374
  # parse the output of that into the format Sequel expects.
302
375
  def schema_parse_table(table_name, opts)
303
- m = output_identifier_meth
376
+ m = output_identifier_meth(opts[:dataset])
304
377
  parse_pragma(table_name, opts).map do |row|
305
378
  [m.call(row.delete(:name)), row]
306
379
  end
307
380
  end
308
381
 
382
+ # Backbone of the tables and views support.
383
+ def tables_and_views(filter, opts)
384
+ m = output_identifier_meth
385
+ metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(filter).map{|r| m.call(r[:name])}
386
+ end
387
+
309
388
  # SQLite uses the integer data type even for bignums. This is because they
310
389
  # are both stored internally as text, and converted when returned from
311
390
  # the database. Using an integer type instead of bigint makes it more likely
@@ -317,28 +396,59 @@ module Sequel
317
396
 
318
397
  # Instance methods for datasets that connect to an SQLite database
319
398
  module DatasetMethods
320
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
321
- COMMA_SEPARATOR = ', '.freeze
399
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
322
400
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
323
-
401
+ EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
402
+ NOT_SPACE = Dataset::NOT_SPACE
403
+ COMMA = Dataset::COMMA
404
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
405
+ AS = Dataset::AS
406
+ APOS = Dataset::APOS
407
+ EXTRACT_OPEN = "CAST(strftime(".freeze
408
+ EXTRACT_CLOSE = ') AS '.freeze
409
+ NUMERIC = 'NUMERIC'.freeze
410
+ INTEGER = 'INTEGER'.freeze
411
+ BACKTICK = '`'.freeze
412
+ BLOB_START = "X'".freeze
413
+ HSTAR = "H*".freeze
414
+
324
415
  # SQLite does not support pattern matching via regular expressions.
325
416
  # SQLite is case insensitive (depending on pragma), so use LIKE for
326
417
  # ILIKE.
327
- def complex_expression_sql(op, args)
418
+ def complex_expression_sql_append(sql, op, args)
328
419
  case op
329
420
  when :~, :'!~', :'~*', :'!~*'
330
421
  raise Error, "SQLite does not support pattern matching via regular expressions"
331
- when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
332
- # SQLite is case insensitive for ASCII, and non case sensitive for other character sets
333
- "#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
422
+ when :ILIKE
423
+ super(sql, :LIKE, args.map{|a| SQL::Function.new(:upper, a)})
424
+ when :"NOT LIKE", :"NOT ILIKE"
425
+ sql << NOT_SPACE
426
+ complex_expression_sql_append(sql, (op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)
427
+ when :^
428
+ sql << complex_expression_arg_pairs(args) do |a, b|
429
+ a = literal(a)
430
+ b = literal(b)
431
+ "((~(#{a} & #{b})) & (#{a} | #{b}))"
432
+ end
433
+ when :extract
434
+ part = args.at(0)
435
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
436
+ sql << EXTRACT_OPEN << format << COMMA
437
+ literal_append(sql, args.at(1))
438
+ sql << EXTRACT_CLOSE << (part == :second ? NUMERIC : INTEGER) << PAREN_CLOSE
334
439
  else
335
- super(op, args)
440
+ super
336
441
  end
337
442
  end
338
443
 
339
- # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
340
- def constant_sql(constant)
341
- CONSTANT_MAP[constant] || super
444
+ # SQLite has CURRENT_TIMESTAMP and related constants in UTC instead
445
+ # of in localtime, so convert those constants to local time.
446
+ def constant_sql_append(sql, constant)
447
+ if c = CONSTANT_MAP[constant]
448
+ sql << c
449
+ else
450
+ super
451
+ end
342
452
  end
343
453
 
344
454
  # SQLite performs a TRUNCATE style DELETE if no filter is specified.
@@ -362,8 +472,20 @@ module Sequel
362
472
  end
363
473
 
364
474
  # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
365
- def quoted_identifier(c)
366
- "`#{c}`"
475
+ def quoted_identifier_append(sql, c)
476
+ sql << BACKTICK << c.to_s << BACKTICK
477
+ end
478
+
479
+ # When a qualified column is selected on SQLite and the qualifier
480
+ # is a subselect, the column name used is the full qualified name
481
+ # (including the qualifier) instead of just the column name. To
482
+ # get correct column names, you must use an alias.
483
+ def select(*cols)
484
+ if ((f = @opts[:from]) && f.any?{|t| t.is_a?(Dataset) || (t.is_a?(SQL::AliasedExpression) && t.expression.is_a?(Dataset))}) || ((j = @opts[:join]) && j.any?{|t| t.table.is_a?(Dataset)})
485
+ super(*cols.map{|c| alias_qualified_column(c)})
486
+ else
487
+ super
488
+ end
367
489
  end
368
490
 
369
491
  # SQLite does not support INTERSECT ALL or EXCEPT ALL
@@ -382,31 +504,53 @@ module Sequel
382
504
  end
383
505
 
384
506
  # SQLite supports timezones in literal timestamps, since it stores them
385
- # as text.
507
+ # as text. But using timezones in timestamps breaks SQLite datetime
508
+ # functions, so we allow the user to override the default per database.
386
509
  def supports_timestamp_timezones?
387
- true
510
+ db.use_timestamp_timezones?
388
511
  end
389
512
 
513
+ # SQLite cannot use WHERE 't'.
514
+ def supports_where_true?
515
+ false
516
+ end
517
+
390
518
  private
391
519
 
392
520
  # SQLite uses string literals instead of identifiers in AS clauses.
393
- def as_sql(expression, aliaz)
521
+ def as_sql_append(sql, aliaz)
394
522
  aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
395
- "#{expression} AS #{literal(aliaz.to_s)}"
523
+ sql << AS
524
+ literal_append(sql, aliaz.to_s)
525
+ end
526
+
527
+ # If col is a qualified column, alias it to the same as the column name
528
+ def alias_qualified_column(col)
529
+ case col
530
+ when Symbol
531
+ t, c, a = split_symbol(col)
532
+ if t && !a
533
+ alias_qualified_column(SQL::QualifiedIdentifier.new(t, c))
534
+ else
535
+ col
536
+ end
537
+ when SQL::QualifiedIdentifier
538
+ SQL::AliasedExpression.new(col, col.column)
539
+ else
540
+ col
541
+ end
396
542
  end
397
543
 
398
544
  # SQL fragment specifying a list of identifiers
399
545
  def identifier_list(columns)
400
- columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
546
+ columns.map{|i| quote_identifier(i)}.join(COMMA)
401
547
  end
402
548
 
403
549
  # SQLite uses a preceding X for hex escaping strings
404
- def literal_blob(v)
405
- blob = ''
406
- v.each_byte{|x| blob << sprintf('%02x', x)}
407
- "X'#{blob}'"
550
+ def literal_blob_append(sql, v)
551
+ sql << BLOB_START << v.unpack(HSTAR).first << APOS
408
552
  end
409
-
553
+
410
554
  # SQLite does not support the SQL WITH clause
411
555
  def select_clause_methods
412
556
  SELECT_CLAUSE_METHODS
@@ -416,7 +560,7 @@ module Sequel
416
560
  def select_lock_sql(sql)
417
561
  super unless @opts[:lock] == :update
418
562
  end
419
-
563
+
420
564
  # SQLite treats a DELETE with no WHERE clause as a TRUNCATE
421
565
  def _truncate_sql(table)
422
566
  "DELETE FROM #{table}"
@@ -13,26 +13,21 @@ module Sequel
13
13
  TYPE_TRANSLATOR = tt = Class.new do
14
14
  FALSE_VALUES = %w'0 false f no n'.freeze
15
15
  def boolean(s) !FALSE_VALUES.include?(s.downcase) end
16
- def blob(s) ::Sequel::SQL::Blob.new(s) end
17
16
  def integer(s) s.to_i end
18
17
  def float(s) s.to_f end
19
18
  def numeric(s) ::BigDecimal.new(s) rescue s end
20
- def date(s) ::Sequel.string_to_date(s) end
21
- def time(s) ::Sequel.string_to_time(s) end
22
- def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
23
19
  end.new
24
20
 
25
21
  # Hash with string keys and callable values for converting SQLite types.
26
22
  SQLITE_TYPES = {}
27
23
  {
28
- %w'timestamp datetime' => tt.method(:timestamp),
29
- %w'date' => tt.method(:date),
30
- %w'time' => tt.method(:time),
24
+ %w'date' => ::Sequel.method(:string_to_date),
25
+ %w'time' => ::Sequel.method(:string_to_time),
31
26
  %w'bit bool boolean' => tt.method(:boolean),
32
27
  %w'integer smallint mediumint int bigint' => tt.method(:integer),
33
28
  %w'numeric decimal money' => tt.method(:numeric),
34
29
  %w'float double real dec fixed' + ['double precision'] => tt.method(:float),
35
- %w'blob' => tt.method(:blob)
30
+ %w'blob' => ::Sequel::SQL::Blob.method(:new)
36
31
  }.each do |k,v|
37
32
  k.each{|n| SQLITE_TYPES[n] = v}
38
33
  end
@@ -51,6 +46,16 @@ module Sequel
51
46
  end
52
47
 
53
48
  private_class_method :uri_to_options
49
+
50
+ # The conversion procs to use for this database
51
+ attr_reader :conversion_procs
52
+
53
+ def initialize(opts={})
54
+ super
55
+ @conversion_procs = SQLITE_TYPES.dup
56
+ @conversion_procs['timestamp'] = method(:to_application_timestamp)
57
+ @conversion_procs['datetime'] = method(:to_application_timestamp)
58
+ end
54
59
 
55
60
  # Connect to the database. Since SQLite is a file based database,
56
61
  # the only options available are :database (to specify the database
@@ -72,11 +77,6 @@ module Sequel
72
77
  db
73
78
  end
74
79
 
75
- # Return instance of Sequel::SQLite::Dataset with the given options.
76
- def dataset(opts = nil)
77
- SQLite::Dataset.new(self, opts)
78
- end
79
-
80
80
  # Run the given SQL with the given arguments and yield each row.
81
81
  def execute(sql, opts={}, &block)
82
82
  _execute(:select, sql, opts, &block)
@@ -117,7 +117,8 @@ module Sequel
117
117
  synchronize(opts[:server]) do |conn|
118
118
  return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
119
119
  log_args = opts[:arguments]
120
- args = opts.fetch(:arguments, [])
120
+ args = {}
121
+ opts.fetch(:arguments, {}).each{|k, v| args[k] = prepared_statement_argument(v)}
121
122
  case type
122
123
  when :select
123
124
  log_yield(sql, log_args){conn.query(sql, args, &block)}
@@ -173,7 +174,7 @@ module Sequel
173
174
  end
174
175
  end
175
176
  unless cps
176
- cps = conn.prepare(sql)
177
+ cps = log_yield("Preparing #{name}: #{sql}"){conn.prepare(sql)}
177
178
  conn.prepared_statements[name] = [cps, sql]
178
179
  end
179
180
  if block
@@ -204,6 +205,8 @@ module Sequel
204
205
  # Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
205
206
  class Dataset < Sequel::Dataset
206
207
  include ::Sequel::SQLite::DatasetMethods
208
+
209
+ Database::DatasetClass = self
207
210
 
208
211
  PREPARED_ARG_PLACEHOLDER = ':'.freeze
209
212
 
@@ -217,7 +220,7 @@ module Sequel
217
220
  # but with the keys converted to strings.
218
221
  def map_to_prepared_args(hash)
219
222
  args = {}
220
- hash.each{|k,v| args[k.to_s] = v}
223
+ hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
221
224
  args
222
225
  end
223
226
 
@@ -226,7 +229,12 @@ module Sequel
226
229
  # SQLite uses a : before the name of the argument for named
227
230
  # arguments.
228
231
  def prepared_arg(k)
229
- LiteralString.new("#{prepared_arg_placeholder}#{k}")
232
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
233
+ end
234
+
235
+ # Always assume a prepared argument.
236
+ def prepared_arg?(k)
237
+ true
230
238
  end
231
239
  end
232
240
 
@@ -287,7 +295,8 @@ module Sequel
287
295
  def fetch_rows(sql)
288
296
  execute(sql) do |result|
289
297
  i = -1
290
- type_procs = result.types.map{|t| SQLITE_TYPES[base_type_name(t)]}
298
+ cps = db.conversion_procs
299
+ type_procs = result.types.map{|t| cps[base_type_name(t)]}
291
300
  cols = result.columns.map{|c| i+=1; [output_identifier(c), i, type_procs[i]]}
292
301
  @columns = cols.map{|c| c.first}
293
302
  result.each do |values|
@@ -314,7 +323,6 @@ module Sequel
314
323
  ps.prepared_statement_name = name
315
324
  db.prepared_statements[name] = ps
316
325
  end
317
- ps.prepared_sql
318
326
  ps
319
327
  end
320
328
 
@@ -326,8 +334,8 @@ module Sequel
326
334
  end
327
335
 
328
336
  # Quote the string using the adapter class method.
329
- def literal_string(v)
330
- "'#{::SQLite3::Database.quote(v)}'"
337
+ def literal_string_append(sql, v)
338
+ sql << "'" << ::SQLite3::Database.quote(v) << "'"
331
339
  end
332
340
 
333
341
  # SQLite uses a : before the name of the argument as a placeholder.
@@ -9,11 +9,6 @@ module Sequel
9
9
  module DatabaseMethods
10
10
  include Sequel::MySQL::DatabaseMethods
11
11
 
12
- # Return instance of Sequel::Swift::MySQL::Dataset with the given opts.
13
- def dataset(opts=nil)
14
- Sequel::Swift::MySQL::Dataset.new(self, opts)
15
- end
16
-
17
12
  private
18
13
 
19
14
  # The database name for the given database.
@@ -25,11 +20,21 @@ module Sequel
25
20
  def schema_column_type(db_type)
26
21
  db_type == 'tinyint(1)' ? :boolean : super
27
22
  end
23
+
24
+ # By default, MySQL 'where id is null' selects the last inserted id.
25
+ # Turn that off unless explicitly enabled.
26
+ def setup_connection(conn)
27
+ super
28
+ sql = "SET SQL_AUTO_IS_NULL=0"
29
+ log_yield(sql){conn.execute(sql)} unless opts[:auto_is_null]
30
+ conn
31
+ end
28
32
  end
29
33
 
30
34
  # Dataset class for MySQL datasets accessed via Swift.
31
35
  class Dataset < Swift::Dataset
32
36
  include Sequel::MySQL::DatasetMethods
37
+ APOS = Dataset::APOS
33
38
 
34
39
  # Use execute_insert to execute the replace_sql.
35
40
  def replace(*args)
@@ -39,8 +44,8 @@ module Sequel
39
44
  private
40
45
 
41
46
  # Use Swift's escape method for quoting.
42
- def literal_string(s)
43
- db.synchronize{|c| "'#{c.escape(s)}'"}
47
+ def literal_string_append(sql, s)
48
+ sql << APOS << db.synchronize{|c| c.escape(s)} << APOS
44
49
  end
45
50
  end
46
51
  end
@@ -41,11 +41,6 @@ module Sequel
41
41
  end
42
42
  end
43
43
 
44
- # Return instance of Sequel::Swift::Postgres::Dataset with the given opts.
45
- def dataset(opts=nil)
46
- Sequel::Swift::Postgres::Dataset.new(self, opts)
47
- end
48
-
49
44
  # Run the SELECT SQL on the database and yield the rows
50
45
  def execute(sql, opts={})
51
46
  synchronize(opts[:server]) do |conn|
@@ -8,10 +8,12 @@ module Sequel
8
8
  # Database instance methods for SQLite databases accessed via Swift.
9
9
  module DatabaseMethods
10
10
  include Sequel::SQLite::DatabaseMethods
11
-
12
- # Return instance of Sequel::Swift::SQL::Dataset with the given opts.
13
- def dataset(opts=nil)
14
- Sequel::Swift::SQLite::Dataset.new(self, opts)
11
+
12
+ # Set the correct pragmas on the connection.
13
+ def connect(opts)
14
+ c = super
15
+ connection_pragmas.each{|s| log_yield(s){c.execute(s)}}
16
+ c
15
17
  end
16
18
  end
17
19
 
@@ -22,8 +24,8 @@ module Sequel
22
24
  private
23
25
 
24
26
  # Use Swift's escape method for quoting.
25
- def literal_string(s)
26
- db.synchronize{|c| "#{c.escape(s)}"}
27
+ def literal_string_append(sql, s)
28
+ sql << db.synchronize{|c| c.escape(s)}
27
29
  end
28
30
  end
29
31
  end
@@ -15,16 +15,19 @@ module Sequel
15
15
  DATABASE_SETUP = {:postgres=>proc do |db|
16
16
  Sequel.ts_require 'adapters/swift/postgres'
17
17
  db.extend(Sequel::Swift::Postgres::DatabaseMethods)
18
+ db.dataset_class = Sequel::Swift::Postgres::Dataset
18
19
  db.swift_class = ::Swift::DB::Postgres
19
20
  end,
20
21
  :mysql=>proc do |db|
21
22
  Sequel.ts_require 'adapters/swift/mysql'
22
23
  db.extend(Sequel::Swift::MySQL::DatabaseMethods)
24
+ db.dataset_class = Sequel::Swift::MySQL::Dataset
23
25
  db.swift_class = ::Swift::DB::Mysql
24
26
  end,
25
27
  :sqlite=>proc do |db|
26
28
  Sequel.ts_require 'adapters/swift/sqlite'
27
29
  db.extend(Sequel::Swift::SQLite::DatabaseMethods)
30
+ db.dataset_class = Sequel::Swift::SQLite::Dataset
28
31
  db.swift_class = ::Swift::DB::Sqlite3
29
32
  end,
30
33
  }
@@ -59,11 +62,6 @@ module Sequel
59
62
  setup_connection(swift_class.new(server_opts(server)))
60
63
  end
61
64
 
62
- # Return a Sequel::Swift::Dataset object for this database.
63
- def dataset(opts = nil)
64
- Swift::Dataset.new(self, opts)
65
- end
66
-
67
65
  # Execute the given SQL, yielding a Swift::Result if a block is given.
68
66
  def execute(sql, opts={})
69
67
  synchronize(opts[:server]) do |conn|
@@ -134,6 +132,8 @@ module Sequel
134
132
  end
135
133
 
136
134
  class Dataset < Sequel::Dataset
135
+ Database::DatasetClass = self
136
+
137
137
  # Set the columns and yield the hashes to the block.
138
138
  def fetch_rows(sql, &block)
139
139
  execute(sql) do |res|