sequel 3.21.0 → 3.41.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 (363) hide show
  1. data/CHANGELOG +1131 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +106 -65
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +68 -52
  7. data/doc/advanced_associations.rdoc +201 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/bin_sequel.rdoc +144 -0
  10. data/doc/cheat_sheet.rdoc +21 -21
  11. data/doc/core_extensions.rdoc +374 -0
  12. data/doc/dataset_basics.rdoc +10 -10
  13. data/doc/dataset_filtering.rdoc +54 -42
  14. data/doc/mass_assignment.rdoc +56 -0
  15. data/doc/migration.rdoc +89 -557
  16. data/doc/model_hooks.rdoc +80 -29
  17. data/doc/object_model.rdoc +576 -0
  18. data/doc/opening_databases.rdoc +121 -53
  19. data/doc/prepared_statements.rdoc +41 -15
  20. data/doc/querying.rdoc +137 -56
  21. data/doc/reflection.rdoc +36 -10
  22. data/doc/release_notes/3.22.0.txt +39 -0
  23. data/doc/release_notes/3.23.0.txt +172 -0
  24. data/doc/release_notes/3.24.0.txt +420 -0
  25. data/doc/release_notes/3.25.0.txt +88 -0
  26. data/doc/release_notes/3.26.0.txt +88 -0
  27. data/doc/release_notes/3.27.0.txt +82 -0
  28. data/doc/release_notes/3.28.0.txt +304 -0
  29. data/doc/release_notes/3.29.0.txt +459 -0
  30. data/doc/release_notes/3.30.0.txt +135 -0
  31. data/doc/release_notes/3.31.0.txt +146 -0
  32. data/doc/release_notes/3.32.0.txt +202 -0
  33. data/doc/release_notes/3.33.0.txt +157 -0
  34. data/doc/release_notes/3.34.0.txt +671 -0
  35. data/doc/release_notes/3.35.0.txt +144 -0
  36. data/doc/release_notes/3.36.0.txt +245 -0
  37. data/doc/release_notes/3.37.0.txt +338 -0
  38. data/doc/release_notes/3.38.0.txt +234 -0
  39. data/doc/release_notes/3.39.0.txt +237 -0
  40. data/doc/release_notes/3.40.0.txt +73 -0
  41. data/doc/release_notes/3.41.0.txt +155 -0
  42. data/doc/schema_modification.rdoc +585 -0
  43. data/doc/sharding.rdoc +99 -8
  44. data/doc/sql.rdoc +154 -112
  45. data/doc/testing.rdoc +169 -0
  46. data/doc/thread_safety.rdoc +17 -0
  47. data/doc/transactions.rdoc +137 -0
  48. data/doc/validations.rdoc +1 -1
  49. data/doc/virtual_rows.rdoc +78 -43
  50. data/lib/sequel/adapters/ado/access.rb +334 -0
  51. data/lib/sequel/adapters/ado/mssql.rb +18 -12
  52. data/lib/sequel/adapters/ado.rb +69 -21
  53. data/lib/sequel/adapters/amalgalite.rb +17 -14
  54. data/lib/sequel/adapters/cubrid.rb +143 -0
  55. data/lib/sequel/adapters/db2.rb +178 -85
  56. data/lib/sequel/adapters/dbi.rb +19 -21
  57. data/lib/sequel/adapters/do/mysql.rb +17 -11
  58. data/lib/sequel/adapters/do/postgres.rb +2 -61
  59. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  60. data/lib/sequel/adapters/do.rb +20 -39
  61. data/lib/sequel/adapters/firebird.rb +33 -210
  62. data/lib/sequel/adapters/ibmdb.rb +452 -0
  63. data/lib/sequel/adapters/informix.rb +6 -29
  64. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  65. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  66. data/lib/sequel/adapters/jdbc/db2.rb +78 -0
  67. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  68. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  69. data/lib/sequel/adapters/jdbc/h2.rb +68 -31
  70. data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
  71. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  73. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  74. data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
  75. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  76. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  77. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  78. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  79. data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
  80. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  81. data/lib/sequel/adapters/jdbc.rb +308 -114
  82. data/lib/sequel/adapters/mock.rb +372 -0
  83. data/lib/sequel/adapters/mysql.rb +130 -230
  84. data/lib/sequel/adapters/mysql2.rb +64 -63
  85. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  86. data/lib/sequel/adapters/odbc.rb +29 -10
  87. data/lib/sequel/adapters/openbase.rb +7 -11
  88. data/lib/sequel/adapters/oracle.rb +364 -57
  89. data/lib/sequel/adapters/postgres.rb +379 -145
  90. data/lib/sequel/adapters/shared/access.rb +236 -11
  91. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  92. data/lib/sequel/adapters/shared/db2.rb +353 -0
  93. data/lib/sequel/adapters/shared/firebird.rb +231 -0
  94. data/lib/sequel/adapters/shared/informix.rb +53 -0
  95. data/lib/sequel/adapters/shared/mssql.rb +412 -116
  96. data/lib/sequel/adapters/shared/mysql.rb +396 -89
  97. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
  98. data/lib/sequel/adapters/shared/oracle.rb +262 -45
  99. data/lib/sequel/adapters/shared/postgres.rb +688 -279
  100. data/lib/sequel/adapters/shared/progress.rb +5 -8
  101. data/lib/sequel/adapters/shared/sqlite.rb +317 -96
  102. data/lib/sequel/adapters/sqlite.rb +109 -46
  103. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  104. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  105. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  106. data/lib/sequel/adapters/swift.rb +31 -27
  107. data/lib/sequel/adapters/tinytds.rb +177 -25
  108. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  109. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  110. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  111. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  112. data/lib/sequel/ast_transformer.rb +194 -0
  113. data/lib/sequel/connection_pool/sharded_single.rb +23 -13
  114. data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
  115. data/lib/sequel/connection_pool/single.rb +11 -3
  116. data/lib/sequel/connection_pool/threaded.rb +69 -20
  117. data/lib/sequel/connection_pool.rb +20 -18
  118. data/lib/sequel/core.rb +143 -11
  119. data/lib/sequel/database/connecting.rb +66 -29
  120. data/lib/sequel/database/dataset.rb +4 -4
  121. data/lib/sequel/database/dataset_defaults.rb +63 -2
  122. data/lib/sequel/database/logging.rb +7 -2
  123. data/lib/sequel/database/misc.rb +192 -51
  124. data/lib/sequel/database/query.rb +299 -114
  125. data/lib/sequel/database/schema_generator.rb +103 -36
  126. data/lib/sequel/database/schema_methods.rb +310 -69
  127. data/lib/sequel/dataset/actions.rb +344 -90
  128. data/lib/sequel/dataset/features.rb +101 -8
  129. data/lib/sequel/dataset/graph.rb +42 -15
  130. data/lib/sequel/dataset/misc.rb +52 -25
  131. data/lib/sequel/dataset/mutation.rb +12 -7
  132. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  133. data/lib/sequel/dataset/query.rb +322 -68
  134. data/lib/sequel/dataset/sql.rb +716 -293
  135. data/lib/sequel/dataset.rb +8 -0
  136. data/lib/sequel/exceptions.rb +4 -0
  137. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  138. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  139. data/lib/sequel/extensions/blank.rb +4 -0
  140. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  141. data/lib/sequel/extensions/connection_validator.rb +109 -0
  142. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  143. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  144. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  145. data/lib/sequel/extensions/inflector.rb +4 -0
  146. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  147. data/lib/sequel/extensions/migration.rb +100 -11
  148. data/lib/sequel/extensions/named_timezones.rb +9 -0
  149. data/lib/sequel/extensions/null_dataset.rb +94 -0
  150. data/lib/sequel/extensions/pagination.rb +4 -0
  151. data/lib/sequel/extensions/pg_array.rb +539 -0
  152. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  153. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  154. data/lib/sequel/extensions/pg_hstore.rb +334 -0
  155. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  156. data/lib/sequel/extensions/pg_inet.rb +113 -0
  157. data/lib/sequel/extensions/pg_interval.rb +191 -0
  158. data/lib/sequel/extensions/pg_json.rb +212 -0
  159. data/lib/sequel/extensions/pg_range.rb +513 -0
  160. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  161. data/lib/sequel/extensions/pg_row.rb +576 -0
  162. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  163. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  164. data/lib/sequel/extensions/pretty_table.rb +17 -72
  165. data/lib/sequel/extensions/query.rb +8 -4
  166. data/lib/sequel/extensions/query_literals.rb +79 -0
  167. data/lib/sequel/extensions/schema_caching.rb +76 -0
  168. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  169. data/lib/sequel/extensions/select_remove.rb +39 -0
  170. data/lib/sequel/extensions/server_block.rb +140 -0
  171. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  172. data/lib/sequel/extensions/sql_expr.rb +8 -110
  173. data/lib/sequel/extensions/string_date_time.rb +4 -0
  174. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  175. data/lib/sequel/extensions/to_dot.rb +99 -83
  176. data/lib/sequel/model/associations.rb +1263 -409
  177. data/lib/sequel/model/base.rb +624 -171
  178. data/lib/sequel/model/errors.rb +1 -1
  179. data/lib/sequel/model/exceptions.rb +24 -2
  180. data/lib/sequel/model/inflections.rb +1 -1
  181. data/lib/sequel/model.rb +30 -11
  182. data/lib/sequel/no_core_ext.rb +2 -0
  183. data/lib/sequel/plugins/active_model.rb +13 -1
  184. data/lib/sequel/plugins/association_pks.rb +137 -16
  185. data/lib/sequel/plugins/caching.rb +25 -18
  186. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  187. data/lib/sequel/plugins/composition.rb +44 -12
  188. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  189. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  190. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  191. data/lib/sequel/plugins/dirty.rb +214 -0
  192. data/lib/sequel/plugins/eager_each.rb +59 -0
  193. data/lib/sequel/plugins/force_encoding.rb +6 -6
  194. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  195. data/lib/sequel/plugins/identity_map.rb +134 -15
  196. data/lib/sequel/plugins/instance_filters.rb +10 -0
  197. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  198. data/lib/sequel/plugins/json_serializer.rb +77 -6
  199. data/lib/sequel/plugins/list.rb +13 -2
  200. data/lib/sequel/plugins/many_through_many.rb +103 -51
  201. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  202. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  203. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  204. data/lib/sequel/plugins/pg_row.rb +121 -0
  205. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  206. data/lib/sequel/plugins/prepared_statements.rb +168 -0
  207. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  208. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  209. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  210. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  211. data/lib/sequel/plugins/schema.rb +8 -3
  212. data/lib/sequel/plugins/serialization.rb +98 -49
  213. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  214. data/lib/sequel/plugins/sharding.rb +21 -54
  215. data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
  216. data/lib/sequel/plugins/static_cache.rb +99 -0
  217. data/lib/sequel/plugins/subclasses.rb +29 -3
  218. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  219. data/lib/sequel/plugins/timestamps.rb +1 -1
  220. data/lib/sequel/plugins/touch.rb +18 -6
  221. data/lib/sequel/plugins/tree.rb +3 -3
  222. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  223. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  224. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  225. data/lib/sequel/plugins/validation_helpers.rb +57 -4
  226. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  227. data/lib/sequel/sql.rb +721 -141
  228. data/lib/sequel/timezones.rb +67 -40
  229. data/lib/sequel/version.rb +1 -1
  230. data/spec/adapters/db2_spec.rb +146 -0
  231. data/spec/adapters/firebird_spec.rb +53 -39
  232. data/spec/adapters/mssql_spec.rb +261 -66
  233. data/spec/adapters/mysql_spec.rb +597 -352
  234. data/spec/adapters/oracle_spec.rb +92 -117
  235. data/spec/adapters/postgres_spec.rb +2103 -341
  236. data/spec/adapters/spec_helper.rb +16 -6
  237. data/spec/adapters/sqlite_spec.rb +237 -76
  238. data/spec/core/connection_pool_spec.rb +270 -141
  239. data/spec/core/database_spec.rb +980 -524
  240. data/spec/core/dataset_spec.rb +1658 -1243
  241. data/spec/core/expression_filters_spec.rb +741 -334
  242. data/spec/core/mock_adapter_spec.rb +453 -0
  243. data/spec/core/object_graph_spec.rb +88 -129
  244. data/spec/core/schema_generator_spec.rb +4 -4
  245. data/spec/core/schema_spec.rb +556 -53
  246. data/spec/core/spec_helper.rb +7 -48
  247. data/spec/core_extensions_spec.rb +626 -0
  248. data/spec/extensions/active_model_spec.rb +13 -0
  249. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  250. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  251. data/spec/extensions/association_dependencies_spec.rb +15 -25
  252. data/spec/extensions/association_pks_spec.rb +227 -33
  253. data/spec/extensions/association_proxies_spec.rb +4 -4
  254. data/spec/extensions/boolean_readers_spec.rb +25 -25
  255. data/spec/extensions/caching_spec.rb +47 -51
  256. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  257. data/spec/extensions/columns_introspection_spec.rb +91 -0
  258. data/spec/extensions/composition_spec.rb +18 -13
  259. data/spec/extensions/connection_validator_spec.rb +118 -0
  260. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  261. data/spec/extensions/constraint_validations_spec.rb +316 -0
  262. data/spec/extensions/dataset_associations_spec.rb +199 -0
  263. data/spec/extensions/defaults_setter_spec.rb +88 -0
  264. data/spec/extensions/dirty_spec.rb +155 -0
  265. data/spec/extensions/eager_each_spec.rb +34 -0
  266. data/spec/extensions/eval_inspect_spec.rb +67 -0
  267. data/spec/extensions/force_encoding_spec.rb +4 -2
  268. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  269. data/spec/extensions/identity_map_spec.rb +142 -24
  270. data/spec/extensions/inflector_spec.rb +0 -4
  271. data/spec/extensions/instance_filters_spec.rb +11 -21
  272. data/spec/extensions/instance_hooks_spec.rb +72 -0
  273. data/spec/extensions/json_serializer_spec.rb +49 -12
  274. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  275. data/spec/extensions/list_spec.rb +49 -40
  276. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  277. data/spec/extensions/many_through_many_spec.rb +368 -254
  278. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  279. data/spec/extensions/migration_spec.rb +140 -35
  280. data/spec/extensions/named_timezones_spec.rb +29 -11
  281. data/spec/extensions/nested_attributes_spec.rb +268 -89
  282. data/spec/extensions/null_dataset_spec.rb +85 -0
  283. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  284. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  285. data/spec/extensions/pg_array_spec.rb +324 -0
  286. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  287. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  288. data/spec/extensions/pg_hstore_spec.rb +200 -0
  289. data/spec/extensions/pg_inet_spec.rb +47 -0
  290. data/spec/extensions/pg_interval_spec.rb +72 -0
  291. data/spec/extensions/pg_json_spec.rb +99 -0
  292. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  293. data/spec/extensions/pg_range_spec.rb +395 -0
  294. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  295. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  296. data/spec/extensions/pg_row_spec.rb +340 -0
  297. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  298. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  299. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  300. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  301. data/spec/extensions/prepared_statements_spec.rb +87 -0
  302. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  303. data/spec/extensions/pretty_table_spec.rb +7 -6
  304. data/spec/extensions/query_literals_spec.rb +167 -0
  305. data/spec/extensions/query_spec.rb +9 -3
  306. data/spec/extensions/rcte_tree_spec.rb +50 -43
  307. data/spec/extensions/schema_caching_spec.rb +41 -0
  308. data/spec/extensions/schema_dumper_spec.rb +434 -49
  309. data/spec/extensions/schema_spec.rb +30 -49
  310. data/spec/extensions/select_remove_spec.rb +38 -0
  311. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  312. data/spec/extensions/serialization_spec.rb +86 -48
  313. data/spec/extensions/server_block_spec.rb +90 -0
  314. data/spec/extensions/sharding_spec.rb +69 -143
  315. data/spec/extensions/single_table_inheritance_spec.rb +65 -29
  316. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  317. data/spec/extensions/spec_helper.rb +34 -67
  318. data/spec/extensions/split_array_nil_spec.rb +24 -0
  319. data/spec/extensions/sql_expr_spec.rb +29 -60
  320. data/spec/extensions/static_cache_spec.rb +145 -0
  321. data/spec/extensions/subclasses_spec.rb +14 -0
  322. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  323. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  324. data/spec/extensions/timestamps_spec.rb +6 -6
  325. data/spec/extensions/to_dot_spec.rb +8 -11
  326. data/spec/extensions/touch_spec.rb +53 -14
  327. data/spec/extensions/tree_spec.rb +11 -26
  328. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  329. data/spec/extensions/update_primary_key_spec.rb +30 -24
  330. data/spec/extensions/validation_class_methods_spec.rb +77 -68
  331. data/spec/extensions/validation_helpers_spec.rb +48 -37
  332. data/spec/extensions/xml_serializer_spec.rb +29 -4
  333. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  334. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  335. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  336. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  337. data/spec/integration/associations_test.rb +659 -21
  338. data/spec/integration/database_test.rb +25 -5
  339. data/spec/integration/dataset_test.rb +729 -141
  340. data/spec/integration/eager_loader_test.rb +38 -54
  341. data/spec/integration/migrator_test.rb +2 -3
  342. data/spec/integration/model_test.rb +81 -2
  343. data/spec/integration/plugin_test.rb +871 -160
  344. data/spec/integration/prepared_statement_test.rb +272 -128
  345. data/spec/integration/schema_test.rb +377 -57
  346. data/spec/integration/spec_helper.rb +49 -32
  347. data/spec/integration/timezone_test.rb +38 -12
  348. data/spec/integration/transaction_test.rb +183 -8
  349. data/spec/integration/type_test.rb +40 -8
  350. data/spec/model/association_reflection_spec.rb +207 -8
  351. data/spec/model/associations_spec.rb +1206 -693
  352. data/spec/model/base_spec.rb +334 -100
  353. data/spec/model/dataset_methods_spec.rb +45 -27
  354. data/spec/model/eager_loading_spec.rb +828 -692
  355. data/spec/model/hooks_spec.rb +355 -67
  356. data/spec/model/model_spec.rb +251 -179
  357. data/spec/model/plugins_spec.rb +24 -13
  358. data/spec/model/record_spec.rb +506 -232
  359. data/spec/model/spec_helper.rb +16 -77
  360. data/spec/model/validations_spec.rb +38 -2
  361. data/spec/rcov.opts +2 -0
  362. metadata +223 -81
  363. data/spec/core/core_sql_spec.rb +0 -451
@@ -0,0 +1,79 @@
1
+ # The query_literals extension changes Sequel's default behavior of
2
+ # the select, order and group methods so that if the first argument
3
+ # is a regular string, it is treated as a literal string, with the
4
+ # rest of the arguments (if any) treated as placeholder values. This
5
+ # allows you to write code such as:
6
+ #
7
+ # DB[:table].select('a, b, ?', 2).group('a, b').order('c')
8
+ #
9
+ # The default Sequel behavior would literalize that as:
10
+ #
11
+ # SELECT 'a, b, ?', 2 FROM table GROUP BY 'a, b' ORDER BY 'c'
12
+ #
13
+ # Using this extension changes the literalization to:
14
+ #
15
+ # SELECT a, b, 2, FROM table GROUP BY a, b ORDER BY c
16
+ #
17
+ # This extension makes select, group, and order methods operate
18
+ # like filter methods, which support the same interface.
19
+ #
20
+ # There are very few places where Sequel's default behavior is
21
+ # desirable in this area, but for backwards compatibility, the
22
+ # defaults won't be changed until the next major release.
23
+ #
24
+ # You can load this extension into specific datasets:
25
+ #
26
+ # ds = DB[:table]
27
+ # ds.extension(:query_literals)
28
+ #
29
+ # Or you can load it into all of a database's datasets, which
30
+ # is probably the desired behavior if you are using this extension:
31
+ #
32
+ # DB.extension(:query_literals)
33
+
34
+ module Sequel
35
+ # The QueryLiterals module can be used to make select, group, and
36
+ # order methods operate similar to the filter methods if the first
37
+ # argument is a plain string, treating it like a literal string,
38
+ # with any remaining arguments treated as placeholder values.
39
+ #
40
+ # This adds such support to the following methods: select, select_append,
41
+ # select_group, select_more, group, group_and_count, order, order_append,
42
+ # and order_more.
43
+ #
44
+ # Note that if you pass a block to these methods, it will use the default
45
+ # implementation without the special literal handling.
46
+ module QueryLiterals
47
+ %w'select select_append select_group select_more group group_and_count order order_append order_more'.each do |m|
48
+ class_eval(<<-END, __FILE__, __LINE__ + 1)
49
+ def #{m}(*args)
50
+ if !block_given? && (l = query_literal(args))
51
+ super(l)
52
+ else
53
+ super
54
+ end
55
+ end
56
+ END
57
+ end
58
+
59
+ private
60
+
61
+ # If the first argument is a plain string, return a literal string
62
+ # if there are no additional args or a placeholder literal string with
63
+ # the remaining args. Otherwise, return nil.
64
+ def query_literal(args)
65
+ case (s = args[0])
66
+ when LiteralString, SQL::Blob
67
+ nil
68
+ when String
69
+ if args.length == 1
70
+ LiteralString.new(s)
71
+ else
72
+ SQL::PlaceholderLiteralString.new(s, args[1..-1])
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ Dataset.register_extension(:query_literals, QueryLiterals)
79
+ end
@@ -0,0 +1,76 @@
1
+ # The schema_caching extension adds a few methods to Sequel::Database
2
+ # that make it easy to dump the parsed schema information to a file,
3
+ # and load it from that file. Loading the schema information from a
4
+ # dumped file is faster than parsing it from the database, so this
5
+ # can save bootup time for applications with large numbers of models.
6
+ #
7
+ # Basic usage in application code:
8
+ #
9
+ # Sequel.extension :schema_caching
10
+ #
11
+ # DB = Sequel.connect('...')
12
+ #
13
+ # DB.load_schema_cache('/path/to/schema.dump')
14
+ #
15
+ # # load model files
16
+ #
17
+ # Then, whenever the database schema is modified, write a new cached
18
+ # file. You can do that with <tt>bin/sequel</tt>'s -S option:
19
+ #
20
+ # bin/sequel -S /path/to/schema.dump postgres://...
21
+ #
22
+ # Alternatively, if you don't want to dump the schema information for
23
+ # all tables, and you don't worry about race conditions, you can
24
+ # choose to use the following in your application code:
25
+ #
26
+ # Sequel.extension :schema_caching
27
+ #
28
+ # DB = Sequel.connect('...')
29
+ #
30
+ # DB.load_schema_cache?('/path/to/schema.dump')
31
+ #
32
+ # # load model files
33
+ #
34
+ # DB.dump_schema_cache?('/path/to/schema.dump')
35
+ #
36
+ # With this method, you just have to delete the schema dump file if
37
+ # the schema is modified, and the application will recreate it for you
38
+ # using just the tables that your models use.
39
+ #
40
+ # Note that it is up to the application to ensure that the dumped
41
+ # cached schema reflects the current state of the database. Sequel
42
+ # does no checking to ensure this, as checking would take time and the
43
+ # purpose of this code is to take a shortcut.
44
+ #
45
+ # The cached schema is dumped in Marshal format, since it is the fastest
46
+ # and it handles all ruby objects used in the schema hash. Because of this,
47
+ # you should not attempt to load the schema from a untrusted file.
48
+
49
+ module Sequel
50
+ class Database
51
+ # Dump the cached schema to the filename given in Marshal format.
52
+ def dump_schema_cache(file)
53
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(@schemas))}
54
+ nil
55
+ end
56
+
57
+ # Dump the cached schema to the filename given unless the file
58
+ # already exists.
59
+ def dump_schema_cache?(file)
60
+ dump_schema_cache(file) unless File.exist?(file)
61
+ end
62
+
63
+ # Replace the schema cache with the data from the given file, which
64
+ # should be in Marshal format.
65
+ def load_schema_cache(file)
66
+ @schemas = Marshal.load(File.read(file))
67
+ nil
68
+ end
69
+
70
+ # Replace the schema cache with the data from the given file if the
71
+ # file exists.
72
+ def load_schema_cache?(file)
73
+ load_schema_cache(file) if File.exist?(file)
74
+ end
75
+ end
76
+ end
@@ -3,47 +3,86 @@
3
3
  # database (which can be the same type or a different type than
4
4
  # the current database). The main interface is through
5
5
  # Sequel::Database#dump_schema_migration.
6
+ #
7
+ # To load the extension:
8
+ #
9
+ # Sequel.extension :schema_dumper
10
+
11
+ Sequel.extension :eval_inspect
6
12
 
7
13
  module Sequel
8
14
  class Database
15
+ # Dump foreign key constraints for all tables as a migration. This complements
16
+ # the :foreign_keys=>false option to dump_schema_migration. This only dumps
17
+ # the constraints (not the columns) using alter_table/add_foreign_key with an
18
+ # array of columns.
19
+ #
20
+ # Note that the migration this produces does not have a down
21
+ # block, so you cannot reverse it.
22
+ def dump_foreign_key_migration(options={})
23
+ ts = tables(options)
24
+ <<END_MIG
25
+ Sequel.migration do
26
+ change do
27
+ #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_foreign_keys(t)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
28
+ end
29
+ end
30
+ END_MIG
31
+ end
32
+
9
33
  # Dump indexes for all tables as a migration. This complements
10
34
  # the :indexes=>false option to dump_schema_migration. Options:
11
- # * :same_db - Create a dump for the same database type, so
12
- # don't ignore errors if the index statements fail.
35
+ # :same_db :: Create a dump for the same database type, so
36
+ # don't ignore errors if the index statements fail.
37
+ # :index_names :: If set to false, don't record names of indexes. If
38
+ # set to :namespace, prepend the table name to the index name if the
39
+ # database does not use a global index namespace.
13
40
  def dump_indexes_migration(options={})
14
41
  ts = tables(options)
15
42
  <<END_MIG
16
43
  Sequel.migration do
17
- up do
44
+ change do
18
45
  #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
19
46
  end
20
-
21
- 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, ' ')}
23
- end
24
47
  end
25
48
  END_MIG
26
49
  end
27
50
 
28
51
  # Return a string that contains a Sequel::Migration subclass that when
29
52
  # run would recreate the database structure. Options:
30
- # * :same_db - Don't attempt to translate database types to ruby types.
31
- # If this isn't set to true, all database types will be translated to
32
- # ruby types, but there is no guarantee that the migration generated
33
- # will yield the same type. Without this set, types that aren't
34
- # recognized will be translated to a string-like type.
35
- # * :indexes - If set to false, don't dump indexes (they can be added
36
- # later via dump_index_migration).
53
+ # :same_db :: Don't attempt to translate database types to ruby types.
54
+ # If this isn't set to true, all database types will be translated to
55
+ # ruby types, but there is no guarantee that the migration generated
56
+ # will yield the same type. Without this set, types that aren't
57
+ # recognized will be translated to a string-like type.
58
+ # :foreign_keys :: If set to false, don't dump foreign_keys (they can be
59
+ # added later via #dump_foreign_key_migration)
60
+ # :indexes :: If set to false, don't dump indexes (they can be added
61
+ # later via #dump_index_migration).
62
+ # :index_names :: If set to false, don't record names of indexes. If
63
+ # set to :namespace, prepend the table name to the index name.
37
64
  def dump_schema_migration(options={})
38
- ts = tables(options)
65
+ options = options.dup
66
+ if options[:indexes] == false && !options.has_key?(:foreign_keys)
67
+ # Unless foreign_keys option is specifically set, disable if indexes
68
+ # are disabled, as foreign keys that point to non-primary keys rely
69
+ # on unique indexes being created first
70
+ options[:foreign_keys] = false
71
+ end
72
+
73
+ ts = sort_dumped_tables(tables(options), options)
74
+ skipped_fks = if sfk = options[:skipped_foreign_keys]
75
+ # Handle skipped foreign keys by adding them at the end via
76
+ # alter_table/add_foreign_key. Note that skipped foreign keys
77
+ # probably result in a broken down migration.
78
+ sfka = sfk.sort_by{|table, fks| table.to_s}.map{|table, fks| dump_add_fk_constraints(table, fks.values)}
79
+ sfka.join("\n\n").gsub(/^/o, ' ') unless sfka.empty?
80
+ end
81
+
39
82
  <<END_MIG
40
83
  Sequel.migration do
41
- up do
42
- #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}
43
- end
44
-
45
- down do
46
- drop_table(#{ts.sort_by{|t| t.to_s}.inspect[1...-1]})
84
+ change do
85
+ #{ts.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}#{"\n \n" if skipped_fks}#{skipped_fks}
47
86
  end
48
87
  end
49
88
  END_MIG
@@ -53,50 +92,34 @@ END_MIG
53
92
  # table's schema. Takes the same options as dump_schema_migration.
54
93
  def dump_table_schema(table, options={})
55
94
  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
95
+ gen = dump_table_generator(table, options)
72
96
  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"
97
+ "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
98
  end
75
99
 
76
100
  private
77
101
 
78
- # If a database default exists and can't be converted, return the string with the inspect
79
- # method modified so that .lit is always appended after it, only if the
80
- # :same_db option is used.
102
+ # If a database default exists and can't be converted, and we are dumping with :same_db,
103
+ # return a string with the inspect method modified a literal string is created if the code is evaled.
81
104
  def column_schema_to_ruby_default_fallback(default, options)
82
105
  if default.is_a?(String) && options[:same_db] && use_column_schema_to_ruby_default_fallback?
83
- default = default.to_s
106
+ default = default.dup
84
107
  def default.inspect
85
- "#{super}.lit"
108
+ "Sequel::LiteralString.new(#{super})"
86
109
  end
87
110
  default
88
111
  end
89
112
  end
90
113
 
91
- # Convert the given name and parsed database schema into an array with a method
92
- # name and arguments to it to pass to a Schema::Generator to recreate the column.
93
- def column_schema_to_generator_opts(name, schema, options)
114
+ # Recreate the column in the passed Schema::Generator from the given name and parsed database schema.
115
+ def recreate_column(name, schema, gen, options)
94
116
  if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
95
117
  type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
118
+ [:table, :key, :on_delete, :on_update, :deferrable].each{|f| type_hash[f] = schema[f] if schema[f]}
96
119
  if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
97
- [:primary_key, name]
120
+ gen.primary_key(name)
98
121
  else
99
- [:primary_key, name, type_hash]
122
+ gen.primary_key(name, type_hash)
100
123
  end
101
124
  else
102
125
  col_opts = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
@@ -109,7 +132,16 @@ END_MIG
109
132
  end
110
133
  col_opts.delete(:default) if col_opts[:default].nil?
111
134
  col_opts[:null] = false if schema[:allow_null] == false
112
- [:column, name, type, col_opts]
135
+ if table = schema[:table]
136
+ [:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]}
137
+ col_opts[:type] = type unless type == Integer || type == 'integer'
138
+ gen.foreign_key(name, table, col_opts)
139
+ else
140
+ gen.column(name, type, col_opts)
141
+ if [Integer, Bignum, Float].include?(type) && schema[:db_type] =~ / unsigned\z/io
142
+ gen.check(Sequel::SQL::Identifier.new(name) >= 0)
143
+ end
144
+ end
113
145
  end
114
146
  end
115
147
 
@@ -118,13 +150,19 @@ END_MIG
118
150
  # database type is not recognized, return it as a String type.
119
151
  def column_schema_to_ruby_type(schema)
120
152
  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
153
+ when /\A(medium|small)?int(?:eger)?(?:\((\d+)\))?( unsigned)?\z/o
154
+ if !$1 && $2 && $2.to_i >= 10 && $3
155
+ # Unsigned integer type with 10 digits can potentially contain values which
156
+ # don't fit signed integer type, so use bigint type in target database.
157
+ {:type=>Bignum}
158
+ else
159
+ {:type=>Integer}
160
+ end
161
+ when /\Atinyint(?:\((\d+)\))?(?: unsigned)?\z/o
124
162
  {:type =>schema[:type] == :boolean ? TrueClass : Integer}
125
163
  when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/o
126
164
  {:type=>Bignum}
127
- when /\A(?:real|float|double(?: precision)?)\z/o
165
+ when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/o
128
166
  {:type=>Float}
129
167
  when 'boolean'
130
168
  {:type=>TrueClass}
@@ -149,34 +187,191 @@ END_MIG
149
187
  {:type=>BigDecimal, :size=>(s.empty? ? nil : s)}
150
188
  when /\A(?:bytea|(?:tiny|medium|long)?blob|(?:var)?binary)(?:\((\d+)\))?\z/o
151
189
  {:type=>File, :size=>($1.to_i if $1)}
152
- when 'year'
190
+ when /\A(?:year|(?:int )?identity)\z/o
153
191
  {:type=>Integer}
154
192
  else
155
193
  {:type=>String}
156
194
  end
157
195
  end
158
196
 
197
+ # For the table and foreign key metadata array, return an alter_table
198
+ # string that would add the foreign keys if run in a migration.
199
+ def dump_add_fk_constraints(table, fks)
200
+ sfks = "alter_table(#{table.inspect}) do\n"
201
+ sfks << create_table_generator do
202
+ fks.sort_by{|fk| fk[:columns].map{|c| c.to_s}}.each do |fk|
203
+ foreign_key fk[:columns], fk
204
+ end
205
+ end.dump_constraints.gsub(/^foreign_key /, ' add_foreign_key ')
206
+ sfks << "\nend"
207
+ end
208
+
209
+ # For the table given, get the list of foreign keys and return an alter_table
210
+ # string that would add the foreign keys if run in a migration.
211
+ def dump_table_foreign_keys(table, options={})
212
+ begin
213
+ fks = foreign_key_list(table, options).sort_by{|fk| fk[:columns].map{|c| c.to_s}}
214
+ rescue Sequel::NotImplemented
215
+ return ''
216
+ end
217
+
218
+ if fks.empty?
219
+ ''
220
+ else
221
+ dump_add_fk_constraints(table, fks)
222
+ end
223
+ end
224
+
225
+ # Return a Schema::Generator object that will recreate the
226
+ # table's schema. Takes the same options as dump_schema_migration.
227
+ def dump_table_generator(table, options={})
228
+ table = table.value.to_s if table.is_a?(SQL::Identifier)
229
+ raise(Error, "must provide table as a Symbol, String, or Sequel::SQL::Identifier") unless [String, Symbol].any?{|c| table.is_a?(c)}
230
+ s = schema(table).dup
231
+ pks = s.find_all{|x| x.last[:primary_key] == true}.map{|x| x.first}
232
+ options = options.merge(:single_pk=>true) if pks.length == 1
233
+ m = method(:recreate_column)
234
+ im = method(:index_to_generator_opts)
235
+
236
+ if options[:indexes] != false
237
+ begin
238
+ indexes = indexes(table).sort_by{|k,v| k.to_s}
239
+ rescue Sequel::NotImplemented
240
+ nil
241
+ end
242
+ end
243
+
244
+ if options[:foreign_keys] != false
245
+ begin
246
+ fk_list = foreign_key_list(table)
247
+
248
+ if (sfk = options[:skipped_foreign_keys]) && (sfkt = sfk[table])
249
+ fk_list.delete_if{|fk| sfkt.has_key?(fk[:columns])}
250
+ end
251
+
252
+ composite_fks, single_fks = fk_list.partition{|h| h[:columns].length > 1}
253
+ fk_hash = {}
254
+
255
+ single_fks.each do |fk|
256
+ column = fk.delete(:columns).first
257
+ fk.delete(:name)
258
+ fk_hash[column] = fk
259
+ end
260
+
261
+ s = s.map do |name, info|
262
+ if fk_info = fk_hash[name]
263
+ [name, fk_info.merge(info)]
264
+ else
265
+ [name, info]
266
+ end
267
+ end
268
+ rescue Sequel::NotImplemented
269
+ nil
270
+ end
271
+ end
272
+
273
+ create_table_generator do
274
+ s.each{|name, info| m.call(name, info, self, options)}
275
+ primary_key(pks) if !@primary_key && pks.length > 0
276
+ indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))} if indexes
277
+ composite_fks.each{|fk| send(:foreign_key, fk[:columns], fk)} if composite_fks
278
+ end
279
+ end
280
+
159
281
  # Return a string that containing add_index/drop_index method calls for
160
282
  # creating the index migration.
161
283
  def dump_table_indexes(table, meth, options={})
162
- return '' unless respond_to?(:indexes)
284
+ begin
285
+ indexes = indexes(table).sort_by{|k,v| k.to_s}
286
+ rescue Sequel::NotImplemented
287
+ return ''
288
+ end
163
289
  im = method(:index_to_generator_opts)
164
- indexes = indexes(table).sort_by{|k,v| k.to_s}
165
- gen = Schema::Generator.new(self) do
166
- indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts))}
290
+ gen = create_table_generator do
291
+ indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))}
167
292
  end
168
293
  gen.dump_indexes(meth=>table, :ignore_errors=>!options[:same_db])
169
294
  end
170
295
 
171
296
  # Convert the parsed index information into options to the Generators index method.
172
- def index_to_generator_opts(table, name, index_opts)
297
+ def index_to_generator_opts(table, name, index_opts, options={})
173
298
  h = {}
174
- h[:name] = name unless default_index_name(table, index_opts[:columns]) == name.to_s
299
+ if options[:index_names] != false && default_index_name(table, index_opts[:columns]) != name.to_s
300
+ if options[:index_names] == :namespace && !global_index_namespace?
301
+ h[:name] = "#{table}_#{name}".to_sym
302
+ else
303
+ h[:name] = name
304
+ end
305
+ end
175
306
  h[:unique] = true if index_opts[:unique]
176
307
  h
177
308
  end
309
+
310
+ # Sort the tables so that referenced tables are created before tables that
311
+ # reference them, and then by name. If foreign keys are disabled, just sort by name.
312
+ def sort_dumped_tables(tables, options={})
313
+ sort_topologically = if options[:foreign_keys] != false
314
+ begin
315
+ foreign_key_list(:some_table_that_does_not_exist)
316
+ true
317
+ rescue Sequel::NotImplemented
318
+ false
319
+ rescue
320
+ true
321
+ end
322
+ end
323
+
324
+ if sort_topologically
325
+ table_fks = {}
326
+ tables.each{|t| table_fks[t] = foreign_key_list(t)}
327
+ # Remove self referential foreign keys, not important when sorting.
328
+ table_fks.each{|t, fks| fks.delete_if{|fk| fk[:table] == t}}
329
+ tables, skipped_foreign_keys = sort_dumped_tables_topologically(table_fks, [])
330
+ options[:skipped_foreign_keys] = skipped_foreign_keys
331
+ tables
332
+ else
333
+ tables.sort_by{|t| t.to_s}
334
+ end
335
+ end
336
+
337
+ # Do a topological sort of tables, so that referenced tables
338
+ # come before referencing tables. Returns an array of sorted
339
+ # tables and a hash of skipped foreign keys. The hash will be
340
+ # empty unless there are circular dependencies.
341
+ def sort_dumped_tables_topologically(table_fks, sorted_tables)
342
+ skipped_foreign_keys = {}
343
+
344
+ until table_fks.empty?
345
+ this_loop = []
346
+
347
+ table_fks.each do |table, fks|
348
+ fks.delete_if{|fk| !table_fks.has_key?(fk[:table])}
349
+ this_loop << table if fks.empty?
350
+ end
351
+
352
+ if this_loop.empty?
353
+ # No tables were changed this round, there must be a circular dependency.
354
+ # Break circular dependency by picking the table with the least number of
355
+ # outstanding foreign keys and skipping those foreign keys.
356
+ # The skipped foreign keys will be added at the end of the
357
+ # migration.
358
+ skip_table, skip_fks = table_fks.sort_by{|table, fks| [fks.length, table.to_s]}.first
359
+ skip_fks_hash = skipped_foreign_keys[skip_table] = {}
360
+ skip_fks.each{|fk| skip_fks_hash[fk[:columns]] = fk}
361
+ this_loop << skip_table
362
+ end
363
+
364
+ # Add sorted tables from this loop to the final list
365
+ sorted_tables.concat(this_loop.sort_by{|t| t.to_s})
366
+
367
+ # Remove tables that were handled this loop
368
+ this_loop.each{|t| table_fks.delete(t)}
369
+ end
370
+
371
+ [sorted_tables, skipped_foreign_keys]
372
+ end
178
373
 
179
- # Don't use the "...".lit fallback on MySQL, since the defaults it uses aren't
374
+ # Don't use a literal string fallback on MySQL, since the defaults it uses aren't
180
375
  # valid literal SQL values.
181
376
  def use_column_schema_to_ruby_default_fallback?
182
377
  database_type != :mysql
@@ -190,6 +385,10 @@ END_MIG
190
385
  def dump_columns
191
386
  strings = []
192
387
  cols = columns.dup
388
+ cols.each do |x|
389
+ x.delete(:on_delete) if x[:on_delete] == :no_action
390
+ x.delete(:on_update) if x[:on_update] == :no_action
391
+ end
193
392
  if pkn = primary_key_name
194
393
  cols.delete_if{|x| x[:name] == pkn}
195
394
  pk = @primary_key.dup
@@ -200,12 +399,17 @@ END_MIG
200
399
  cols.each do |c|
201
400
  c = c.dup
202
401
  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}"
402
+ strings << if table = c.delete(:table)
403
+ c.delete(:type) if c[:type] == Integer || c[:type] == 'integer'
404
+ "foreign_key #{name.inspect}, #{table.inspect}#{opts_inspect(c)}"
207
405
  else
208
- "column #{name.inspect}, #{type.inspect}#{opts}"
406
+ type = c.delete(:type)
407
+ opts = opts_inspect(c)
408
+ if type.is_a?(Class)
409
+ "#{type.name} #{name.inspect}#{opts}"
410
+ else
411
+ "column #{name.inspect}, #{type.inspect}#{opts}"
412
+ end
209
413
  end
210
414
  end
211
415
  strings.join("\n")
@@ -226,6 +430,13 @@ END_MIG
226
430
  else
227
431
  "#{name ? "constraint #{name.inspect}," : 'check'} #{c[:check].map{|x| x.inspect}.join(', ')}"
228
432
  end
433
+ when :foreign_key
434
+ c.delete(:on_delete) if c[:on_delete] == :no_action
435
+ c.delete(:on_update) if c[:on_update] == :no_action
436
+ c.delete(:deferrable) unless c[:deferrable]
437
+ cols = c.delete(:columns)
438
+ table = c.delete(:table)
439
+ "#{type} #{cols.inspect}, #{table.inspect}#{opts_inspect(c)}"
229
440
  else
230
441
  cols = c.delete(:columns)
231
442
  "#{type} #{cols.inspect}#{opts_inspect(c)}"
@@ -251,24 +462,19 @@ END_MIG
251
462
  "index #{cols.inspect}#{opts_inspect(c)}"
252
463
  end
253
464
  end
465
+ is = is.reverse if options[:drop_index]
254
466
  is.join("\n")
255
467
  end
256
468
 
257
469
  private
258
470
 
471
+ # Return a string that converts the given options into one
472
+ # suitable for literal ruby code, handling default values
473
+ # that don't default to a literal interpretation.
259
474
  def opts_inspect(opts)
260
475
  if opts[:default]
261
476
  opts = opts.dup
262
- de = case d = opts.delete(:default)
263
- when BigDecimal, Sequel::SQL::Blob
264
- "#{d.class.name}.new(#{d.to_s.inspect})"
265
- when DateTime, Date
266
- "#{d.class.name}.parse(#{d.to_s.inspect})"
267
- when Time
268
- "#{d.class.name}.parse(#{d.strftime('%H:%M:%S').inspect})"
269
- else
270
- d.inspect
271
- end
477
+ de = Sequel.eval_inspect(opts.delete(:default))
272
478
  ", :default=>#{de}#{", #{opts.inspect[1...-1]}" if opts.length > 0}"
273
479
  else
274
480
  ", #{opts.inspect[1...-1]}" if opts.length > 0
@@ -0,0 +1,39 @@
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
+ # To load the extension:
6
+ #
7
+ # Sequel.extension :select_remove
8
+
9
+ module Sequel
10
+ class Dataset
11
+ # Remove columns from the list of selected columns. If any of the currently selected
12
+ # columns use expressions/aliases, this will remove selected columns with the given
13
+ # aliases. It will also remove entries from the selection that match exactly:
14
+ #
15
+ # # Assume columns a, b, and c in items table
16
+ # DB[:items] # SELECT * FROM items
17
+ # DB[:items].select_remove(:c) # SELECT a, b FROM items
18
+ # DB[:items].select(:a, :b___c, :c___b).select_remove(:c) # SELECT a, c AS b FROM items
19
+ # DB[:items].select(:a, :b___c, :c___b).select_remove(:c___b) # SELECT a, b AS c FROM items
20
+ #
21
+ # Note that there are a few cases where this method may not work correctly:
22
+ #
23
+ # * This dataset joins multiple tables and does not have an existing explicit selection.
24
+ # In this case, the code will currently use unqualified column names for all columns
25
+ # the dataset returns, except for the columns given.
26
+ # * This dataset has an existing explicit selection containing an item that returns
27
+ # multiple database columns (e.g. Sequel.expr(:table).*, Sequel.lit('column1, column2')). In this case,
28
+ # the behavior is undefined and this method should not be used.
29
+ #
30
+ # There may be other cases where this method does not work correctly, use it with caution.
31
+ def select_remove(*cols)
32
+ if (sel = @opts[:select]) && !sel.empty?
33
+ select(*(columns.zip(sel).reject{|c, s| cols.include?(c)}.map{|c, s| s} - cols))
34
+ else
35
+ select(*(columns - cols))
36
+ end
37
+ end
38
+ end
39
+ end