sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -6,10 +6,31 @@
6
6
 
7
7
  module Sequel
8
8
  class Database
9
+ # Dump foreign key constraints for all tables as a migration. This complements
10
+ # the :foreign_keys=>false option to dump_schema_migration. This only dumps
11
+ # the constraints (not the columns) using alter_table/add_foreign_key with an
12
+ # array of columns.
13
+ #
14
+ # Note that the migration this produces does not have a down
15
+ # block, so you cannot reverse it.
16
+ def dump_foreign_key_migration(options={})
17
+ ts = tables(options)
18
+ <<END_MIG
19
+ Sequel.migration do
20
+ up do
21
+ #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_foreign_keys(t)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
22
+ end
23
+ end
24
+ END_MIG
25
+ end
26
+
9
27
  # Dump indexes for all tables as a migration. This complements
10
28
  # the :indexes=>false option to dump_schema_migration. Options:
11
29
  # * :same_db - Create a dump for the same database type, so
12
30
  # don't ignore errors if the index statements fail.
31
+ # * :index_names - If set to false, don't record names of indexes. If
32
+ # set to :namespace, prepend the table name to the index name if the
33
+ # database does not use a global index namespace.
13
34
  def dump_indexes_migration(options={})
14
35
  ts = tables(options)
15
36
  <<END_MIG
@@ -19,7 +40,7 @@ Sequel.migration do
19
40
  end
20
41
 
21
42
  down do
22
- #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :drop_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
43
+ #{ts.sort_by{|t| t.to_s}.reverse.map{|t| dump_table_indexes(t, :drop_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
23
44
  end
24
45
  end
25
46
  END_MIG
@@ -32,18 +53,37 @@ END_MIG
32
53
  # ruby types, but there is no guarantee that the migration generated
33
54
  # will yield the same type. Without this set, types that aren't
34
55
  # recognized will be translated to a string-like type.
56
+ # * :foreign_keys - If set to false, don't dump foreign_keys
35
57
  # * :indexes - If set to false, don't dump indexes (they can be added
36
58
  # later via dump_index_migration).
59
+ # * :index_names - If set to false, don't record names of indexes. If
60
+ # set to :namespace, prepend the table name to the index name.
37
61
  def dump_schema_migration(options={})
38
- ts = tables(options)
62
+ options = options.dup
63
+ if options[:indexes] == false && !options.has_key?(:foreign_keys)
64
+ # Unless foreign_keys option is specifically set, disable if indexes
65
+ # are disabled, as foreign keys that point to non-primary keys rely
66
+ # on unique indexes being created first
67
+ options[:foreign_keys] = false
68
+ end
69
+
70
+ ts = sort_dumped_tables(tables(options), options)
71
+ skipped_fks = if sfk = options[:skipped_foreign_keys]
72
+ # Handle skipped foreign keys by adding them at the end via
73
+ # alter_table/add_foreign_key. Note that skipped foreign keys
74
+ # probably result in a broken down migration.
75
+ sfka = sfk.sort_by{|table, fks| table.to_s}.map{|table, fks| dump_add_fk_constraints(table, fks.values)}
76
+ sfka.join("\n\n").gsub(/^/o, ' ') unless sfka.empty?
77
+ end
78
+
39
79
  <<END_MIG
40
80
  Sequel.migration do
41
81
  up do
42
- #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}
82
+ #{ts.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}#{"\n \n" if skipped_fks}#{skipped_fks}
43
83
  end
44
84
 
45
85
  down do
46
- drop_table(#{ts.sort_by{|t| t.to_s}.inspect[1...-1]})
86
+ drop_table(#{ts.reverse.inspect[1...-1]})
47
87
  end
48
88
  end
49
89
  END_MIG
@@ -53,24 +93,9 @@ END_MIG
53
93
  # table's schema. Takes the same options as dump_schema_migration.
54
94
  def dump_table_schema(table, options={})
55
95
  table = table.value.to_s if table.is_a?(SQL::Identifier)
56
- raise(Error, "must provide table as a Symbol, String, or Sequel::SQL::Identifier") unless [String, Symbol].any?{|c| table.is_a?(c)}
57
- s = schema(table).dup
58
- pks = s.find_all{|x| x.last[:primary_key] == true}.map{|x| x.first}
59
- options = options.merge(:single_pk=>true) if pks.length == 1
60
- m = method(:column_schema_to_generator_opts)
61
- im = method(:index_to_generator_opts)
62
- begin
63
- indexes = indexes(table).sort_by{|k,v| k.to_s} if options[:indexes] != false
64
- rescue Sequel::NotImplemented
65
- nil
66
- end
67
- gen = Schema::Generator.new(self) do
68
- s.each{|name, info| send(*m.call(name, info, options))}
69
- primary_key(pks) if !@primary_key && pks.length > 0
70
- indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts))} if indexes
71
- end
96
+ gen = dump_table_generator(table, options)
72
97
  commands = [gen.dump_columns, gen.dump_constraints, gen.dump_indexes].reject{|x| x == ''}.join("\n\n")
73
- "create_table(#{table.inspect}#{', :ignore_index_errors=>true' if !options[:same_db] && options[:indexes] != false && indexes && !indexes.empty?}) do\n#{commands.gsub(/^/o, ' ')}\nend"
98
+ "create_table(#{table.inspect}#{', :ignore_index_errors=>true' if !options[:same_db] && options[:indexes] != false && !gen.indexes.empty?}) do\n#{commands.gsub(/^/o, ' ')}\nend"
74
99
  end
75
100
 
76
101
  private
@@ -82,7 +107,7 @@ END_MIG
82
107
  if default.is_a?(String) && options[:same_db] && use_column_schema_to_ruby_default_fallback?
83
108
  default = default.to_s
84
109
  def default.inspect
85
- "#{super}.lit"
110
+ "#{super}.lit" # core_sql use
86
111
  end
87
112
  default
88
113
  end
@@ -93,6 +118,7 @@ END_MIG
93
118
  def column_schema_to_generator_opts(name, schema, options)
94
119
  if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
95
120
  type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
121
+ [:table, :key, :on_delete, :on_update, :deferrable].each{|f| type_hash[f] = schema[f] if schema[f]}
96
122
  if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
97
123
  [:primary_key, name]
98
124
  else
@@ -109,7 +135,12 @@ END_MIG
109
135
  end
110
136
  col_opts.delete(:default) if col_opts[:default].nil?
111
137
  col_opts[:null] = false if schema[:allow_null] == false
112
- [:column, name, type, col_opts]
138
+ if table = schema[:table]
139
+ [:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]}
140
+ [:foreign_key, name, table, col_opts]
141
+ else
142
+ [:column, name, type, col_opts]
143
+ end
113
144
  end
114
145
  end
115
146
 
@@ -118,9 +149,15 @@ END_MIG
118
149
  # database type is not recognized, return it as a String type.
119
150
  def column_schema_to_ruby_type(schema)
120
151
  case t = schema[:db_type].downcase
121
- when /\A(?:medium|small)?int(?:eger)?(?:\((?:\d+)\))?(?: unsigned)?\z/o
122
- {:type=>Integer}
123
- when /\Atinyint(?:\((\d+)\))?\z/o
152
+ when /\A(medium|small)?int(?:eger)?(?:\((\d+)\))?( unsigned)?\z/o
153
+ if !$1 && $2 && $2.to_i >= 10 && $3
154
+ # Unsigned integer type with 10 digits can potentially contain values which
155
+ # don't fit signed integer type, so use bigint type in target database.
156
+ {:type=>Bignum}
157
+ else
158
+ {:type=>Integer}
159
+ end
160
+ when /\Atinyint(?:\((\d+)\))?(?: unsigned)?\z/o
124
161
  {:type =>schema[:type] == :boolean ? TrueClass : Integer}
125
162
  when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/o
126
163
  {:type=>Bignum}
@@ -149,32 +186,189 @@ END_MIG
149
186
  {:type=>BigDecimal, :size=>(s.empty? ? nil : s)}
150
187
  when /\A(?:bytea|(?:tiny|medium|long)?blob|(?:var)?binary)(?:\((\d+)\))?\z/o
151
188
  {:type=>File, :size=>($1.to_i if $1)}
152
- when 'year'
189
+ when /\A(?:year|(?:int )?identity)\z/o
153
190
  {:type=>Integer}
154
191
  else
155
192
  {:type=>String}
156
193
  end
157
194
  end
158
195
 
196
+ # For the table and foreign key metadata array, return an alter_table
197
+ # string that would add the foreign keys if run in a migration.
198
+ def dump_add_fk_constraints(table, fks)
199
+ sfks = "alter_table(#{table.inspect}) do\n"
200
+ sfks << Schema::Generator.new(self) do
201
+ fks.sort_by{|fk| fk[:columns].map{|c| c.to_s}}.each do |fk|
202
+ foreign_key fk[:columns], fk
203
+ end
204
+ end.dump_constraints.gsub(/^foreign_key /, ' add_foreign_key ')
205
+ sfks << "\nend"
206
+ end
207
+
208
+ # For the table given, get the list of foreign keys and return an alter_table
209
+ # string that would add the foreign keys if run in a migration.
210
+ def dump_table_foreign_keys(table, options={})
211
+ begin
212
+ fks = foreign_key_list(table, options).sort_by{|fk| fk[:columns].map{|c| c.to_s}}
213
+ rescue Sequel::NotImplemented
214
+ return ''
215
+ end
216
+
217
+ if fks.empty?
218
+ ''
219
+ else
220
+ dump_add_fk_constraints(table, fks)
221
+ end
222
+ end
223
+
224
+ # Return a Schema::Generator object that will recreate the
225
+ # table's schema. Takes the same options as dump_schema_migration.
226
+ def dump_table_generator(table, options={})
227
+ table = table.value.to_s if table.is_a?(SQL::Identifier)
228
+ raise(Error, "must provide table as a Symbol, String, or Sequel::SQL::Identifier") unless [String, Symbol].any?{|c| table.is_a?(c)}
229
+ s = schema(table).dup
230
+ pks = s.find_all{|x| x.last[:primary_key] == true}.map{|x| x.first}
231
+ options = options.merge(:single_pk=>true) if pks.length == 1
232
+ m = method(:column_schema_to_generator_opts)
233
+ im = method(:index_to_generator_opts)
234
+
235
+ if options[:indexes] != false
236
+ begin
237
+ indexes = indexes(table).sort_by{|k,v| k.to_s}
238
+ rescue Sequel::NotImplemented
239
+ nil
240
+ end
241
+ end
242
+
243
+ if options[:foreign_keys] != false
244
+ begin
245
+ fk_list = foreign_key_list(table)
246
+
247
+ if (sfk = options[:skipped_foreign_keys]) && (sfkt = sfk[table])
248
+ fk_list.delete_if{|fk| sfkt.has_key?(fk[:columns])}
249
+ end
250
+
251
+ composite_fks, single_fks = fk_list.partition{|h| h[:columns].length > 1}
252
+ fk_hash = {}
253
+
254
+ single_fks.each do |fk|
255
+ column = fk.delete(:columns).first
256
+ fk.delete(:name)
257
+ fk_hash[column] = fk
258
+ end
259
+
260
+ s = s.map do |name, info|
261
+ if fk_info = fk_hash[name]
262
+ [name, fk_info.merge(info)]
263
+ else
264
+ [name, info]
265
+ end
266
+ end
267
+ rescue Sequel::NotImplemented
268
+ nil
269
+ end
270
+ end
271
+
272
+ Schema::Generator.new(self) do
273
+ s.each{|name, info| send(*m.call(name, info, options))}
274
+ primary_key(pks) if !@primary_key && pks.length > 0
275
+ indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))} if indexes
276
+ composite_fks.each{|fk| send(:foreign_key, fk[:columns], fk)} if composite_fks
277
+ end
278
+ end
279
+
159
280
  # Return a string that containing add_index/drop_index method calls for
160
281
  # creating the index migration.
161
282
  def dump_table_indexes(table, meth, options={})
162
- return '' unless respond_to?(:indexes)
283
+ begin
284
+ indexes = indexes(table).sort_by{|k,v| k.to_s}
285
+ rescue Sequel::NotImplemented
286
+ return ''
287
+ end
163
288
  im = method(:index_to_generator_opts)
164
- indexes = indexes(table).sort_by{|k,v| k.to_s}
165
289
  gen = Schema::Generator.new(self) do
166
- indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts))}
290
+ indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))}
167
291
  end
168
292
  gen.dump_indexes(meth=>table, :ignore_errors=>!options[:same_db])
169
293
  end
170
294
 
171
295
  # Convert the parsed index information into options to the Generators index method.
172
- def index_to_generator_opts(table, name, index_opts)
296
+ def index_to_generator_opts(table, name, index_opts, options={})
173
297
  h = {}
174
- h[:name] = name unless default_index_name(table, index_opts[:columns]) == name.to_s
298
+ if options[:index_names] != false && default_index_name(table, index_opts[:columns]) != name.to_s
299
+ if options[:index_names] == :namespace && !global_index_namespace?
300
+ h[:name] = "#{table}_#{name}".to_sym
301
+ else
302
+ h[:name] = name
303
+ end
304
+ end
175
305
  h[:unique] = true if index_opts[:unique]
176
306
  h
177
307
  end
308
+
309
+ # Sort the tables so that referenced tables are created before tables that
310
+ # reference them, and then by name. If foreign keys are disabled, just sort by name.
311
+ def sort_dumped_tables(tables, options={})
312
+ sort_topologically = if options[:foreign_keys] != false
313
+ begin
314
+ foreign_key_list(:some_table_that_does_not_exist)
315
+ true
316
+ rescue Sequel::NotImplemented
317
+ false
318
+ rescue
319
+ true
320
+ end
321
+ end
322
+
323
+ if sort_topologically
324
+ table_fks = {}
325
+ tables.each{|t| table_fks[t] = foreign_key_list(t)}
326
+ # Remove self referential foreign keys, not important when sorting.
327
+ table_fks.each{|t, fks| fks.delete_if{|fk| fk[:table] == t}}
328
+ tables, skipped_foreign_keys = sort_dumped_tables_topologically(table_fks, [])
329
+ options[:skipped_foreign_keys] = skipped_foreign_keys
330
+ tables
331
+ else
332
+ tables.sort_by{|t| t.to_s}
333
+ end
334
+ end
335
+
336
+ # Do a topological sort of tables, so that referenced tables
337
+ # come before referencing tables. Returns an array of sorted
338
+ # tables and a hash of skipped foreign keys. The hash will be
339
+ # empty unless there are circular dependencies.
340
+ def sort_dumped_tables_topologically(table_fks, sorted_tables)
341
+ skipped_foreign_keys = {}
342
+
343
+ until table_fks.empty?
344
+ this_loop = []
345
+
346
+ table_fks.each do |table, fks|
347
+ fks.delete_if{|fk| !table_fks.has_key?(fk[:table])}
348
+ this_loop << table if fks.empty?
349
+ end
350
+
351
+ if this_loop.empty?
352
+ # No tables were changed this round, there must be a circular dependency.
353
+ # Break circular dependency by picking the table with the least number of
354
+ # outstanding foreign keys and skipping those foreign keys.
355
+ # The skipped foreign keys will be added at the end of the
356
+ # migration.
357
+ skip_table, skip_fks = table_fks.sort_by{|table, fks| [fks.length, table.to_s]}.first
358
+ skip_fks_hash = skipped_foreign_keys[skip_table] = {}
359
+ skip_fks.each{|fk| skip_fks_hash[fk[:columns]] = fk}
360
+ this_loop << skip_table
361
+ end
362
+
363
+ # Add sorted tables from this loop to the final list
364
+ sorted_tables.concat(this_loop.sort_by{|t| t.to_s})
365
+
366
+ # Remove tables that were handled this loop
367
+ this_loop.each{|t| table_fks.delete(t)}
368
+ end
369
+
370
+ [sorted_tables, skipped_foreign_keys]
371
+ end
178
372
 
179
373
  # Don't use the "...".lit fallback on MySQL, since the defaults it uses aren't
180
374
  # valid literal SQL values.
@@ -190,6 +384,10 @@ END_MIG
190
384
  def dump_columns
191
385
  strings = []
192
386
  cols = columns.dup
387
+ cols.each do |x|
388
+ x.delete(:on_delete) if x[:on_delete] == :no_action
389
+ x.delete(:on_update) if x[:on_update] == :no_action
390
+ end
193
391
  if pkn = primary_key_name
194
392
  cols.delete_if{|x| x[:name] == pkn}
195
393
  pk = @primary_key.dup
@@ -200,12 +398,17 @@ END_MIG
200
398
  cols.each do |c|
201
399
  c = c.dup
202
400
  name = c.delete(:name)
203
- type = c.delete(:type)
204
- opts = opts_inspect(c)
205
- strings << if type.is_a?(Class)
206
- "#{type.name} #{name.inspect}#{opts}"
401
+ strings << if table = c.delete(:table)
402
+ c.delete(:type) if c[:type] == Integer || c[:type] == 'integer'
403
+ "foreign_key #{name.inspect}, #{table.inspect}#{opts_inspect(c)}"
207
404
  else
208
- "column #{name.inspect}, #{type.inspect}#{opts}"
405
+ type = c.delete(:type)
406
+ opts = opts_inspect(c)
407
+ if type.is_a?(Class)
408
+ "#{type.name} #{name.inspect}#{opts}"
409
+ else
410
+ "column #{name.inspect}, #{type.inspect}#{opts}"
411
+ end
209
412
  end
210
413
  end
211
414
  strings.join("\n")
@@ -226,6 +429,13 @@ END_MIG
226
429
  else
227
430
  "#{name ? "constraint #{name.inspect}," : 'check'} #{c[:check].map{|x| x.inspect}.join(', ')}"
228
431
  end
432
+ when :foreign_key
433
+ c.delete(:on_delete) if c[:on_delete] == :no_action
434
+ c.delete(:on_update) if c[:on_update] == :no_action
435
+ c.delete(:deferrable) unless c[:deferrable]
436
+ cols = c.delete(:columns)
437
+ table = c.delete(:table)
438
+ "#{type} #{cols.inspect}, #{table.inspect}#{opts_inspect(c)}"
229
439
  else
230
440
  cols = c.delete(:columns)
231
441
  "#{type} #{cols.inspect}#{opts_inspect(c)}"
@@ -251,11 +461,15 @@ END_MIG
251
461
  "index #{cols.inspect}#{opts_inspect(c)}"
252
462
  end
253
463
  end
464
+ is = is.reverse if options[:drop_index]
254
465
  is.join("\n")
255
466
  end
256
467
 
257
468
  private
258
469
 
470
+ # Return a string that converts the given options into one
471
+ # suitable for literal ruby code, handling default values
472
+ # that don't default to a literal interpretation.
259
473
  def opts_inspect(opts)
260
474
  if opts[:default]
261
475
  opts = opts.dup
@@ -0,0 +1,35 @@
1
+ # The select_remove extension adds Sequel::Dataset#select_remove for removing existing selected
2
+ # columns from a dataset. It's not part of Sequel core as it is rarely needed and has
3
+ # some corner cases where it can't work correctly.
4
+
5
+ module Sequel
6
+ class Dataset
7
+ # Remove columns from the list of selected columns. If any of the currently selected
8
+ # columns use expressions/aliases, this will remove selected columns with the given
9
+ # aliases. It will also remove entries from the selection that match exactly:
10
+ #
11
+ # # Assume columns a, b, and c in items table
12
+ # DB[:items] # SELECT * FROM items
13
+ # DB[:items].select_remove(:c) # SELECT a, b FROM items
14
+ # DB[:items].select(:a, :b___c, :c___b).select_remove(:c) # SELECT a, c AS b FROM items
15
+ # DB[:items].select(:a, :b___c, :c___b).select_remove(:c___b) # SELECT a, b AS c FROM items
16
+ #
17
+ # Note that there are a few cases where this method may not work correctly:
18
+ #
19
+ # * This dataset joins multiple tables and does not have an existing explicit selection.
20
+ # In this case, the code will currently use unqualified column names for all columns
21
+ # the dataset returns, except for the columns given.
22
+ # * This dataset has an existing explicit selection containing an item that returns
23
+ # multiple database columns (e.g. :table.*, 'column1, column2'.lit). In this case,
24
+ # the behavior is undefined and this method should not be used.
25
+ #
26
+ # There may be other cases where this method does not work correctly, use it with caution.
27
+ def select_remove(*cols)
28
+ if (sel = @opts[:select]) && !sel.empty?
29
+ select(*(columns.zip(sel).reject{|c, s| cols.include?(c)}.map{|c, s| s} - cols))
30
+ else
31
+ select(*(columns - cols))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,139 @@
1
+ # The server_block extension adds the Database#with_server method, which takes a shard
2
+ # argument and a block, and makes it so that access inside the block will use the
3
+ # specified shard by default.
4
+ #
5
+ # First, you need to enable it on the database object:
6
+ #
7
+ # Sequel.extension :server_block
8
+ # DB.extend Sequel::ServerBlock
9
+ #
10
+ # Then you can call with_server:
11
+ #
12
+ # DB.with_server(:shard1) do
13
+ # DB[:a].all # Uses shard1
14
+ # DB[:a].server(:shard2).all # Uses shard2
15
+ # end
16
+ # DB[:a].all # Uses default
17
+ #
18
+ # You can even nest calls to with_server:
19
+ #
20
+ # DB.with_server(:shard1) do
21
+ # DB[:a].all # Uses shard1
22
+ # DB.with_server(:shard2) do
23
+ # DB[:a].all # Uses shard2
24
+ # end
25
+ # DB[:a].all # Uses shard1
26
+ # end
27
+ # DB[:a].all # Uses default
28
+ #
29
+ # Note this this extension assumes the following shard names should use the
30
+ # server/shard passed to with_server: :default, nil, :read_only. All other
31
+ # shard names will cause the standard behavior to be used.
32
+
33
+ module Sequel
34
+ module ServerBlock
35
+ # Enable the server block on the connection pool, choosing the correct
36
+ # extension depending on whether the connection pool is threaded or not.
37
+ # Also defines the with_server method on the receiver for easy use.
38
+ def self.extended(db)
39
+ pool = db.pool
40
+ if defined?(ShardedThreadedConnectionPool) && pool.is_a?(ShardedThreadedConnectionPool)
41
+ pool.extend(ThreadedServerBlock)
42
+ pool.instance_variable_set(:@default_servers, {})
43
+ else
44
+ pool.extend(UnthreadedServerBlock)
45
+ pool.instance_variable_set(:@default_servers, [])
46
+ end
47
+ end
48
+
49
+ # Delegate to the connection pool
50
+ def with_server(server, &block)
51
+ pool.with_server(server, &block)
52
+ end
53
+ end
54
+
55
+ # Adds with_server support for the sharded single connection pool.
56
+ module UnthreadedServerBlock
57
+ # Set a default server/shard to use inside the block.
58
+ def with_server(server)
59
+ begin
60
+ set_default_server(server)
61
+ yield
62
+ ensure
63
+ clear_default_server
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Make the given server the new default server.
70
+ def set_default_server(server)
71
+ @default_servers << server
72
+ end
73
+
74
+ # Remove the current default server, restoring the
75
+ # previous default server.
76
+ def clear_default_server
77
+ @default_servers.pop
78
+ end
79
+
80
+ # Use the server given to with_server if appropriate.
81
+ def pick_server(server)
82
+ if @default_servers.empty?
83
+ super
84
+ else
85
+ case server
86
+ when :default, nil, :read_only
87
+ @default_servers.last
88
+ else
89
+ super
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # Adds with_server support for the sharded threaded connection pool.
96
+ module ThreadedServerBlock
97
+ # Set a default server/shard to use inside the block for the current
98
+ # thread.
99
+ def with_server(server)
100
+ begin
101
+ set_default_server(server)
102
+ yield
103
+ ensure
104
+ clear_default_server
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ # Make the given server the new default server for the current thread.
111
+ def set_default_server(server)
112
+ sync{(@default_servers[Thread.current] ||= [])} << server
113
+ end
114
+
115
+ # Remove the current default server for the current thread, restoring the
116
+ # previous default server.
117
+ def clear_default_server
118
+ t = Thread.current
119
+ a = sync{@default_servers[t]}
120
+ a.pop
121
+ sync{@default_servers.delete(t)} if a.empty?
122
+ end
123
+
124
+ # Use the server given to with_server for the given thread, if appropriate.
125
+ def pick_server(server)
126
+ a = sync{@default_servers[Thread.current]}
127
+ if !a || a.empty?
128
+ super
129
+ else
130
+ case server
131
+ when :default, nil, :read_only
132
+ a.last
133
+ else
134
+ super
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end