sequel 3.21.0 → 3.32.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 (234) hide show
  1. data/CHANGELOG +539 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -6
  4. data/Rakefile +37 -19
  5. data/bin/sequel +1 -5
  6. data/doc/advanced_associations.rdoc +17 -0
  7. data/doc/association_basics.rdoc +293 -43
  8. data/doc/dataset_basics.rdoc +4 -4
  9. data/doc/dataset_filtering.rdoc +8 -0
  10. data/doc/mass_assignment.rdoc +54 -0
  11. data/doc/migration.rdoc +15 -538
  12. data/doc/model_hooks.rdoc +72 -27
  13. data/doc/opening_databases.rdoc +89 -50
  14. data/doc/prepared_statements.rdoc +40 -13
  15. data/doc/reflection.rdoc +8 -2
  16. data/doc/release_notes/3.22.0.txt +39 -0
  17. data/doc/release_notes/3.23.0.txt +172 -0
  18. data/doc/release_notes/3.24.0.txt +420 -0
  19. data/doc/release_notes/3.25.0.txt +88 -0
  20. data/doc/release_notes/3.26.0.txt +88 -0
  21. data/doc/release_notes/3.27.0.txt +82 -0
  22. data/doc/release_notes/3.28.0.txt +304 -0
  23. data/doc/release_notes/3.29.0.txt +459 -0
  24. data/doc/release_notes/3.30.0.txt +135 -0
  25. data/doc/release_notes/3.31.0.txt +146 -0
  26. data/doc/release_notes/3.32.0.txt +202 -0
  27. data/doc/schema_modification.rdoc +547 -0
  28. data/doc/sharding.rdoc +7 -1
  29. data/doc/testing.rdoc +129 -0
  30. data/doc/transactions.rdoc +137 -0
  31. data/doc/virtual_rows.rdoc +2 -2
  32. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  33. data/lib/sequel/adapters/ado.rb +40 -18
  34. data/lib/sequel/adapters/amalgalite.rb +17 -9
  35. data/lib/sequel/adapters/db2.rb +175 -86
  36. data/lib/sequel/adapters/dbi.rb +15 -15
  37. data/lib/sequel/adapters/do/mysql.rb +5 -7
  38. data/lib/sequel/adapters/do/postgres.rb +0 -5
  39. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  40. data/lib/sequel/adapters/do.rb +17 -36
  41. data/lib/sequel/adapters/firebird.rb +27 -208
  42. data/lib/sequel/adapters/ibmdb.rb +448 -0
  43. data/lib/sequel/adapters/informix.rb +6 -23
  44. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  45. data/lib/sequel/adapters/jdbc/db2.rb +52 -0
  46. data/lib/sequel/adapters/jdbc/derby.rb +260 -0
  47. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  48. data/lib/sequel/adapters/jdbc/h2.rb +66 -30
  49. data/lib/sequel/adapters/jdbc/hsqldb.rb +177 -0
  50. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  51. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  52. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  53. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  54. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  56. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  57. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  58. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  59. data/lib/sequel/adapters/jdbc.rb +231 -78
  60. data/lib/sequel/adapters/mock.rb +339 -0
  61. data/lib/sequel/adapters/mysql.rb +85 -184
  62. data/lib/sequel/adapters/mysql2.rb +26 -25
  63. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  64. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  65. data/lib/sequel/adapters/odbc.rb +28 -9
  66. data/lib/sequel/adapters/openbase.rb +3 -5
  67. data/lib/sequel/adapters/oracle.rb +355 -53
  68. data/lib/sequel/adapters/postgres.rb +194 -33
  69. data/lib/sequel/adapters/shared/access.rb +32 -9
  70. data/lib/sequel/adapters/shared/db2.rb +328 -0
  71. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  72. data/lib/sequel/adapters/shared/informix.rb +53 -0
  73. data/lib/sequel/adapters/shared/mssql.rb +278 -107
  74. data/lib/sequel/adapters/shared/mysql.rb +203 -65
  75. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  76. data/lib/sequel/adapters/shared/oracle.rb +237 -46
  77. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  78. data/lib/sequel/adapters/shared/progress.rb +5 -8
  79. data/lib/sequel/adapters/shared/sqlite.rb +200 -50
  80. data/lib/sequel/adapters/sqlite.rb +29 -21
  81. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  82. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  83. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  84. data/lib/sequel/adapters/swift.rb +5 -5
  85. data/lib/sequel/adapters/tinytds.rb +160 -19
  86. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  87. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  88. data/lib/sequel/ast_transformer.rb +190 -0
  89. data/lib/sequel/connection_pool/threaded.rb +3 -2
  90. data/lib/sequel/connection_pool.rb +1 -1
  91. data/lib/sequel/core.rb +68 -5
  92. data/lib/sequel/database/connecting.rb +5 -6
  93. data/lib/sequel/database/dataset.rb +4 -4
  94. data/lib/sequel/database/dataset_defaults.rb +59 -1
  95. data/lib/sequel/database/logging.rb +1 -1
  96. data/lib/sequel/database/misc.rb +104 -19
  97. data/lib/sequel/database/query.rb +163 -59
  98. data/lib/sequel/database/schema_generator.rb +17 -7
  99. data/lib/sequel/database/schema_methods.rb +59 -29
  100. data/lib/sequel/dataset/actions.rb +240 -76
  101. data/lib/sequel/dataset/features.rb +91 -8
  102. data/lib/sequel/dataset/graph.rb +38 -11
  103. data/lib/sequel/dataset/misc.rb +50 -23
  104. data/lib/sequel/dataset/mutation.rb +12 -7
  105. data/lib/sequel/dataset/prepared_statements.rb +59 -27
  106. data/lib/sequel/dataset/query.rb +236 -43
  107. data/lib/sequel/dataset/sql.rb +676 -267
  108. data/lib/sequel/dataset.rb +8 -0
  109. data/lib/sequel/exceptions.rb +4 -0
  110. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  111. data/lib/sequel/extensions/migration.rb +6 -4
  112. data/lib/sequel/extensions/named_timezones.rb +5 -0
  113. data/lib/sequel/extensions/query.rb +1 -1
  114. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  115. data/lib/sequel/extensions/to_dot.rb +95 -83
  116. data/lib/sequel/model/associations.rb +1081 -344
  117. data/lib/sequel/model/base.rb +420 -133
  118. data/lib/sequel/model/errors.rb +1 -1
  119. data/lib/sequel/model/exceptions.rb +5 -1
  120. data/lib/sequel/model.rb +20 -10
  121. data/lib/sequel/plugins/active_model.rb +13 -1
  122. data/lib/sequel/plugins/association_pks.rb +22 -4
  123. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  124. data/lib/sequel/plugins/composition.rb +43 -10
  125. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  126. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  127. data/lib/sequel/plugins/force_encoding.rb +6 -6
  128. data/lib/sequel/plugins/identity_map.rb +114 -7
  129. data/lib/sequel/plugins/many_through_many.rb +68 -7
  130. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  131. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  132. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  133. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  134. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  135. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  136. data/lib/sequel/plugins/serialization.rb +98 -49
  137. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  138. data/lib/sequel/plugins/sharding.rb +21 -54
  139. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  140. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  141. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  142. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  143. data/lib/sequel/sql.rb +109 -52
  144. data/lib/sequel/timezones.rb +57 -38
  145. data/lib/sequel/version.rb +1 -1
  146. data/spec/adapters/db2_spec.rb +146 -0
  147. data/spec/adapters/mssql_spec.rb +135 -30
  148. data/spec/adapters/mysql_spec.rb +244 -212
  149. data/spec/adapters/oracle_spec.rb +67 -77
  150. data/spec/adapters/postgres_spec.rb +267 -78
  151. data/spec/adapters/spec_helper.rb +6 -5
  152. data/spec/adapters/sqlite_spec.rb +35 -21
  153. data/spec/core/connection_pool_spec.rb +71 -92
  154. data/spec/core/core_sql_spec.rb +21 -30
  155. data/spec/core/database_spec.rb +789 -512
  156. data/spec/core/dataset_spec.rb +1176 -1061
  157. data/spec/core/expression_filters_spec.rb +159 -42
  158. data/spec/core/mock_adapter_spec.rb +419 -0
  159. data/spec/core/object_graph_spec.rb +86 -114
  160. data/spec/core/schema_generator_spec.rb +3 -3
  161. data/spec/core/schema_spec.rb +298 -38
  162. data/spec/core/spec_helper.rb +6 -48
  163. data/spec/extensions/active_model_spec.rb +13 -0
  164. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  165. data/spec/extensions/association_dependencies_spec.rb +2 -12
  166. data/spec/extensions/association_pks_spec.rb +66 -32
  167. data/spec/extensions/caching_spec.rb +23 -50
  168. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  169. data/spec/extensions/columns_introspection_spec.rb +91 -0
  170. data/spec/extensions/composition_spec.rb +18 -13
  171. data/spec/extensions/dataset_associations_spec.rb +199 -0
  172. data/spec/extensions/defaults_setter_spec.rb +64 -0
  173. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  174. data/spec/extensions/identity_map_spec.rb +114 -22
  175. data/spec/extensions/instance_filters_spec.rb +11 -21
  176. data/spec/extensions/instance_hooks_spec.rb +53 -0
  177. data/spec/extensions/json_serializer_spec.rb +4 -5
  178. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  179. data/spec/extensions/list_spec.rb +17 -39
  180. data/spec/extensions/many_through_many_spec.rb +283 -250
  181. data/spec/extensions/migration_spec.rb +35 -32
  182. data/spec/extensions/named_timezones_spec.rb +23 -3
  183. data/spec/extensions/nested_attributes_spec.rb +97 -88
  184. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  185. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  186. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  187. data/spec/extensions/prepared_statements_spec.rb +53 -0
  188. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  189. data/spec/extensions/pretty_table_spec.rb +1 -6
  190. data/spec/extensions/query_spec.rb +6 -0
  191. data/spec/extensions/rcte_tree_spec.rb +41 -43
  192. data/spec/extensions/schema_dumper_spec.rb +5 -8
  193. data/spec/extensions/schema_spec.rb +13 -21
  194. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  195. data/spec/extensions/serialization_spec.rb +81 -43
  196. data/spec/extensions/sharding_spec.rb +66 -140
  197. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  198. data/spec/extensions/spec_helper.rb +13 -61
  199. data/spec/extensions/sql_expr_spec.rb +20 -60
  200. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  201. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  202. data/spec/extensions/timestamps_spec.rb +6 -6
  203. data/spec/extensions/to_dot_spec.rb +4 -7
  204. data/spec/extensions/touch_spec.rb +13 -14
  205. data/spec/extensions/tree_spec.rb +11 -26
  206. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  207. data/spec/extensions/update_primary_key_spec.rb +30 -24
  208. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  209. data/spec/extensions/validation_helpers_spec.rb +16 -35
  210. data/spec/extensions/xml_serializer_spec.rb +16 -4
  211. data/spec/integration/associations_test.rb +661 -21
  212. data/spec/integration/database_test.rb +4 -2
  213. data/spec/integration/dataset_test.rb +500 -67
  214. data/spec/integration/eager_loader_test.rb +19 -21
  215. data/spec/integration/model_test.rb +80 -1
  216. data/spec/integration/plugin_test.rb +367 -119
  217. data/spec/integration/prepared_statement_test.rb +222 -125
  218. data/spec/integration/schema_test.rb +217 -39
  219. data/spec/integration/spec_helper.rb +39 -30
  220. data/spec/integration/timezone_test.rb +38 -12
  221. data/spec/integration/transaction_test.rb +182 -5
  222. data/spec/integration/type_test.rb +17 -3
  223. data/spec/model/association_reflection_spec.rb +201 -7
  224. data/spec/model/associations_spec.rb +955 -662
  225. data/spec/model/base_spec.rb +275 -95
  226. data/spec/model/dataset_methods_spec.rb +7 -27
  227. data/spec/model/eager_loading_spec.rb +677 -676
  228. data/spec/model/hooks_spec.rb +349 -67
  229. data/spec/model/model_spec.rb +222 -168
  230. data/spec/model/plugins_spec.rb +24 -13
  231. data/spec/model/record_spec.rb +381 -218
  232. data/spec/model/spec_helper.rb +13 -71
  233. data/spec/model/validations_spec.rb +11 -0
  234. metadata +117 -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,35 @@ 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
+ fks = foreign_keys
174
+ self.foreign_keys = false if fks
175
+ transaction do
176
+ if ops.length > 1 && ops.all?{|op| op[:op] == :add_constraint}
177
+ # If you are just doing constraints, apply all of them at the same time,
178
+ # as otherwise all but the last one get lost.
179
+ alter_table_sql_list(table, [{:op=>:add_constraints, :ops=>ops}]).flatten.each{|sql| execute_ddl(sql)}
180
+ else
181
+ # Run each operation separately, as later operations may depend on the
182
+ # results of earlier operations.
183
+ ops.each{|op| alter_table_sql_list(table, [op]).flatten.each{|sql| execute_ddl(sql)}}
184
+ end
185
+ end
186
+ self.foreign_keys = true if fks
187
+ end
188
+
139
189
  # SQLite supports limited table modification. You can add a column
140
190
  # or an index. Dropping columns is supported by copying the table into
141
191
  # a temporary table, dropping the table, and creating a new table without
@@ -161,12 +211,27 @@ module Sequel
161
211
  when :set_column_null
162
212
  duplicate_table(table){|columns| columns.each{|s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s}}
163
213
  when :set_column_type
164
- duplicate_table(table){|columns| columns.each{|s| s[:type] = op[:type] if s[:name].to_s == op[:name].to_s}}
214
+ duplicate_table(table){|columns| columns.each{|s| s.merge!(op) if s[:name].to_s == op[:name].to_s}}
215
+ when :drop_constraint
216
+ case op[:type]
217
+ when :primary_key
218
+ duplicate_table(table){|columns| columns.each{|s| s[:primary_key] = nil}}
219
+ when :foreign_key
220
+ duplicate_table(table){|columns| columns.each{|s| s[:table] = nil}}
221
+ when :unique
222
+ duplicate_table(table)
223
+ else
224
+ raise Error, "Unsupported :type option for drop_constraint: #{op[:type].inspect}"
225
+ end
226
+ when :add_constraint
227
+ duplicate_table(table, :constraints=>[op])
228
+ when :add_constraints
229
+ duplicate_table(table, :constraints=>op[:ops])
165
230
  else
166
- raise Error, "Unsupported ALTER TABLE operation"
231
+ raise Error, "Unsupported ALTER TABLE operation: #{op[:op].inspect}"
167
232
  end
168
233
  end
169
-
234
+
170
235
  # The array of column symbols in the table, except for ones given in opts[:except]
171
236
  def backup_table_name(table, opts={})
172
237
  table = table.gsub('`', '')
@@ -176,17 +241,19 @@ module Sequel
176
241
  end
177
242
  end
178
243
 
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(', ')
244
+ # Surround default with parens to appease SQLite
245
+ def column_definition_default_sql(sql, column)
246
+ sql << " DEFAULT (#{literal(column[:default])})" if column.include?(:default)
182
247
  end
183
-
248
+
184
249
  # Array of PRAGMA SQL statements based on the Database options that should be applied to
185
250
  # new connections.
186
251
  def connection_pragmas
187
252
  ps = []
188
253
  v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
189
254
  ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
255
+ v = typecast_value_boolean(opts.fetch(:case_sensitive_like, 1))
256
+ ps << "PRAGMA case_sensitive_like = #{v ? 1 : 0}"
190
257
  [[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
191
258
  if v = opts[prag]
192
259
  raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
@@ -208,7 +275,7 @@ module Sequel
208
275
  cols.reject!{|c| nono.include? c[:name] }
209
276
  end
210
277
 
211
- if foreign_keys
278
+ begin
212
279
  metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
213
280
  c = cols.find {|co| co[:name] == row[:from] } or next
214
281
  c[:table] = row[:table]
@@ -217,6 +284,9 @@ module Sequel
217
284
  c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
218
285
  # is there any way to get deferrable status?
219
286
  end
287
+ rescue Sequel::DatabaseError
288
+ # Doesn't work correctly on some versions of JDBC SQLite,
289
+ # giving a "query does not return ResultSet" error.
220
290
  end
221
291
  cols
222
292
  end
@@ -231,17 +301,26 @@ module Sequel
231
301
  opts[:old_columns_proc].call(old_columns) if opts[:old_columns_proc]
232
302
 
233
303
  yield def_columns if block_given?
234
- def_columns_str = column_list_sql(def_columns)
304
+
305
+ constraints = (opts[:constraints] || []).dup
306
+ pks = []
307
+ def_columns.each{|c| pks << c[:name] if c[:primary_key]}
308
+ if pks.length > 1
309
+ constraints << {:type=>:primary_key, :columns=>pks}
310
+ def_columns.each{|c| c[:primary_key] = false if c[:primary_key]}
311
+ end
312
+
313
+ def_columns_str = (def_columns.map{|c| column_definition_sql(c)} + constraints.map{|c| constraint_definition_sql(c)}).join(', ')
235
314
  new_columns = old_columns.dup
236
315
  opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
237
316
 
238
317
  qt = quote_schema_table(table)
239
318
  bt = quote_identifier(backup_table_name(qt))
240
319
  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}"
320
+ "ALTER TABLE #{qt} RENAME TO #{bt}",
321
+ "CREATE TABLE #{qt}(#{def_columns_str})",
322
+ "INSERT INTO #{qt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{bt}",
323
+ "DROP TABLE #{bt}"
245
324
  ]
246
325
  indexes(table).each do |name, h|
247
326
  if (h[:columns].map{|x| x.to_s} - new_columns).empty?
@@ -280,7 +359,7 @@ module Sequel
280
359
 
281
360
  # Parse the output of the table_info pragma
282
361
  def parse_pragma(table_name, opts)
283
- metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth.call(table_name)).map do |row|
362
+ metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth(opts[:dataset]).call(table_name)).map do |row|
284
363
  row.delete(:cid)
285
364
  row[:allow_null] = row.delete(:notnull).to_i == 0
286
365
  row[:default] = row.delete(:dflt_value)
@@ -300,12 +379,18 @@ module Sequel
300
379
  # SQLite supports schema parsing using the table_info PRAGMA, so
301
380
  # parse the output of that into the format Sequel expects.
302
381
  def schema_parse_table(table_name, opts)
303
- m = output_identifier_meth
382
+ m = output_identifier_meth(opts[:dataset])
304
383
  parse_pragma(table_name, opts).map do |row|
305
384
  [m.call(row.delete(:name)), row]
306
385
  end
307
386
  end
308
387
 
388
+ # Backbone of the tables and views support.
389
+ def tables_and_views(filter, opts)
390
+ m = output_identifier_meth
391
+ metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(filter).map{|r| m.call(r[:name])}
392
+ end
393
+
309
394
  # SQLite uses the integer data type even for bignums. This is because they
310
395
  # are both stored internally as text, and converted when returned from
311
396
  # the database. Using an integer type instead of bigint makes it more likely
@@ -317,28 +402,59 @@ module Sequel
317
402
 
318
403
  # Instance methods for datasets that connect to an SQLite database
319
404
  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
405
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
322
406
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
323
-
407
+ EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
408
+ NOT_SPACE = Dataset::NOT_SPACE
409
+ COMMA = Dataset::COMMA
410
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
411
+ AS = Dataset::AS
412
+ APOS = Dataset::APOS
413
+ EXTRACT_OPEN = "CAST(strftime(".freeze
414
+ EXTRACT_CLOSE = ') AS '.freeze
415
+ NUMERIC = 'NUMERIC'.freeze
416
+ INTEGER = 'INTEGER'.freeze
417
+ BACKTICK = '`'.freeze
418
+ BLOB_START = "X'".freeze
419
+ HSTAR = "H*".freeze
420
+
324
421
  # SQLite does not support pattern matching via regular expressions.
325
422
  # SQLite is case insensitive (depending on pragma), so use LIKE for
326
423
  # ILIKE.
327
- def complex_expression_sql(op, args)
424
+ def complex_expression_sql_append(sql, op, args)
328
425
  case op
329
426
  when :~, :'!~', :'~*', :'!~*'
330
427
  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))})"
428
+ when :ILIKE
429
+ super(sql, :LIKE, args.map{|a| SQL::Function.new(:upper, a)})
430
+ when :"NOT LIKE", :"NOT ILIKE"
431
+ sql << NOT_SPACE
432
+ complex_expression_sql_append(sql, (op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)
433
+ when :^
434
+ sql << complex_expression_arg_pairs(args) do |a, b|
435
+ a = literal(a)
436
+ b = literal(b)
437
+ "((~(#{a} & #{b})) & (#{a} | #{b}))"
438
+ end
439
+ when :extract
440
+ part = args.at(0)
441
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
442
+ sql << EXTRACT_OPEN << format << COMMA
443
+ literal_append(sql, args.at(1))
444
+ sql << EXTRACT_CLOSE << (part == :second ? NUMERIC : INTEGER) << PAREN_CLOSE
334
445
  else
335
- super(op, args)
446
+ super
336
447
  end
337
448
  end
338
449
 
339
- # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
340
- def constant_sql(constant)
341
- CONSTANT_MAP[constant] || super
450
+ # SQLite has CURRENT_TIMESTAMP and related constants in UTC instead
451
+ # of in localtime, so convert those constants to local time.
452
+ def constant_sql_append(sql, constant)
453
+ if c = CONSTANT_MAP[constant]
454
+ sql << c
455
+ else
456
+ super
457
+ end
342
458
  end
343
459
 
344
460
  # SQLite performs a TRUNCATE style DELETE if no filter is specified.
@@ -362,8 +478,20 @@ module Sequel
362
478
  end
363
479
 
364
480
  # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
365
- def quoted_identifier(c)
366
- "`#{c}`"
481
+ def quoted_identifier_append(sql, c)
482
+ sql << BACKTICK << c.to_s << BACKTICK
483
+ end
484
+
485
+ # When a qualified column is selected on SQLite and the qualifier
486
+ # is a subselect, the column name used is the full qualified name
487
+ # (including the qualifier) instead of just the column name. To
488
+ # get correct column names, you must use an alias.
489
+ def select(*cols)
490
+ 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)})
491
+ super(*cols.map{|c| alias_qualified_column(c)})
492
+ else
493
+ super
494
+ end
367
495
  end
368
496
 
369
497
  # SQLite does not support INTERSECT ALL or EXCEPT ALL
@@ -382,31 +510,53 @@ module Sequel
382
510
  end
383
511
 
384
512
  # SQLite supports timezones in literal timestamps, since it stores them
385
- # as text.
513
+ # as text. But using timezones in timestamps breaks SQLite datetime
514
+ # functions, so we allow the user to override the default per database.
386
515
  def supports_timestamp_timezones?
387
- true
516
+ db.use_timestamp_timezones?
388
517
  end
389
518
 
519
+ # SQLite cannot use WHERE 't'.
520
+ def supports_where_true?
521
+ false
522
+ end
523
+
390
524
  private
391
525
 
392
526
  # SQLite uses string literals instead of identifiers in AS clauses.
393
- def as_sql(expression, aliaz)
527
+ def as_sql_append(sql, aliaz)
394
528
  aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
395
- "#{expression} AS #{literal(aliaz.to_s)}"
529
+ sql << AS
530
+ literal_append(sql, aliaz.to_s)
531
+ end
532
+
533
+ # If col is a qualified column, alias it to the same as the column name
534
+ def alias_qualified_column(col)
535
+ case col
536
+ when Symbol
537
+ t, c, a = split_symbol(col)
538
+ if t && !a
539
+ alias_qualified_column(SQL::QualifiedIdentifier.new(t, c))
540
+ else
541
+ col
542
+ end
543
+ when SQL::QualifiedIdentifier
544
+ SQL::AliasedExpression.new(col, col.column)
545
+ else
546
+ col
547
+ end
396
548
  end
397
549
 
398
550
  # SQL fragment specifying a list of identifiers
399
551
  def identifier_list(columns)
400
- columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
552
+ columns.map{|i| quote_identifier(i)}.join(COMMA)
401
553
  end
402
554
 
403
555
  # 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}'"
556
+ def literal_blob_append(sql, v)
557
+ sql << BLOB_START << v.unpack(HSTAR).first << APOS
408
558
  end
409
-
559
+
410
560
  # SQLite does not support the SQL WITH clause
411
561
  def select_clause_methods
412
562
  SELECT_CLAUSE_METHODS
@@ -416,7 +566,7 @@ module Sequel
416
566
  def select_lock_sql(sql)
417
567
  super unless @opts[:lock] == :update
418
568
  end
419
-
569
+
420
570
  # SQLite treats a DELETE with no WHERE clause as a TRUNCATE
421
571
  def _truncate_sql(table)
422
572
  "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|