sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -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
@@ -320,7 +399,8 @@ module Sequel
320
399
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
321
400
  COMMA_SEPARATOR = ', '.freeze
322
401
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
323
-
402
+ EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
403
+
324
404
  # SQLite does not support pattern matching via regular expressions.
325
405
  # SQLite is case insensitive (depending on pragma), so use LIKE for
326
406
  # ILIKE.
@@ -328,9 +408,21 @@ module Sequel
328
408
  case op
329
409
  when :~, :'!~', :'~*', :'!~*'
330
410
  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))})"
411
+ when :ILIKE
412
+ super(:LIKE, args.map{|a| SQL::Function.new(:upper, a)})
413
+ when :"NOT LIKE", :"NOT ILIKE"
414
+ "NOT #{complex_expression_sql((op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)}"
415
+ when :^
416
+ complex_expression_arg_pairs(args) do |a, b|
417
+ a = literal(a)
418
+ b = literal(b)
419
+ "((~(#{a} & #{b})) & (#{a} | #{b}))"
420
+ end
421
+ when :extract
422
+ part = args.at(0)
423
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
424
+ expr = args.at(1)
425
+ "CAST(strftime(#{format}, #{literal(expr)}) AS #{part == :second ? 'NUMERIC' : 'INTEGER'})"
334
426
  else
335
427
  super(op, args)
336
428
  end
@@ -366,6 +458,18 @@ module Sequel
366
458
  "`#{c}`"
367
459
  end
368
460
 
461
+ # When a qualified column is selected on SQLite and the qualifier
462
+ # is a subselect, the column name used is the full qualified name
463
+ # (including the qualifier) instead of just the column name. To
464
+ # get correct column names, you must use an alias.
465
+ def select(*cols)
466
+ 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)})
467
+ super(*cols.map{|c| alias_qualified_column(c)})
468
+ else
469
+ super
470
+ end
471
+ end
472
+
369
473
  # SQLite does not support INTERSECT ALL or EXCEPT ALL
370
474
  def supports_intersect_except_all?
371
475
  false
@@ -382,11 +486,17 @@ module Sequel
382
486
  end
383
487
 
384
488
  # SQLite supports timezones in literal timestamps, since it stores them
385
- # as text.
489
+ # as text. But using timezones in timestamps breaks SQLite datetime
490
+ # functions, so we allow the user to override the default per database.
386
491
  def supports_timestamp_timezones?
387
- true
492
+ db.use_timestamp_timezones?
388
493
  end
389
494
 
495
+ # SQLite cannot use WHERE 't'.
496
+ def supports_where_true?
497
+ false
498
+ end
499
+
390
500
  private
391
501
 
392
502
  # SQLite uses string literals instead of identifiers in AS clauses.
@@ -394,6 +504,23 @@ module Sequel
394
504
  aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
395
505
  "#{expression} AS #{literal(aliaz.to_s)}"
396
506
  end
507
+
508
+ # If col is a qualified column, alias it to the same as the column name
509
+ def alias_qualified_column(col)
510
+ case col
511
+ when Symbol
512
+ t, c, a = split_symbol(col)
513
+ if t && !a
514
+ alias_qualified_column(SQL::QualifiedIdentifier.new(t, c))
515
+ else
516
+ col
517
+ end
518
+ when SQL::QualifiedIdentifier
519
+ SQL::AliasedExpression.new(col, col.column)
520
+ else
521
+ col
522
+ end
523
+ end
397
524
 
398
525
  # SQL fragment specifying a list of identifiers
399
526
  def identifier_list(columns)
@@ -406,7 +533,7 @@ module Sequel
406
533
  v.each_byte{|x| blob << sprintf('%02x', x)}
407
534
  "X'#{blob}'"
408
535
  end
409
-
536
+
410
537
  # SQLite does not support the SQL WITH clause
411
538
  def select_clause_methods
412
539
  SELECT_CLAUSE_METHODS
@@ -416,7 +543,7 @@ module Sequel
416
543
  def select_lock_sql(sql)
417
544
  super unless @opts[:lock] == :update
418
545
  end
419
-
546
+
420
547
  # SQLite treats a DELETE with no WHERE clause as a TRUNCATE
421
548
  def _truncate_sql(table)
422
549
  "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|
@@ -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,6 +20,15 @@ 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.
@@ -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
 
@@ -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|