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
@@ -1,6 +1,6 @@
1
- module Sequel
2
- Dataset::NON_SQL_OPTIONS << :disable_insert_returning
1
+ Sequel.require 'adapters/utils/pg_types'
3
2
 
3
+ module Sequel
4
4
  # Top level module for holding all PostgreSQL-related modules and classes
5
5
  # for Sequel. There are a few module level accessors that are added via
6
6
  # metaprogramming. These are:
@@ -33,10 +33,10 @@ module Sequel
33
33
  # Array of exceptions that need to be converted. JDBC
34
34
  # uses NativeExceptions, the native adapter uses PGError.
35
35
  CONVERTED_EXCEPTIONS = []
36
-
36
+
37
37
  @client_min_messages = :warning
38
38
  @force_standard_strings = true
39
-
39
+
40
40
  class << self
41
41
  # By default, Sequel sets the minimum level of log messages sent to the client
42
42
  # to WARNING, where PostgreSQL uses a default of NOTICE. This is to avoid a lot
@@ -45,25 +45,71 @@ module Sequel
45
45
  attr_accessor :client_min_messages
46
46
 
47
47
  # By default, Sequel forces the use of standard strings, so that
48
- # '\\' is interpreted as \\ and not \. While PostgreSQL defaults
49
- # to interpreting plain strings as extended strings, this will change
50
- # in a future version of PostgreSQL. Sequel assumes that SQL standard
51
- # strings will be used.
48
+ # '\\' is interpreted as \\ and not \. While PostgreSQL <9.1 defaults
49
+ # to interpreting plain strings, newer versions use standard strings by
50
+ # default. Sequel assumes that SQL standard strings will be used. Setting
51
+ # this to false means Sequel will use the database's default.
52
52
  attr_accessor :force_standard_strings
53
53
  end
54
54
 
55
- # Methods shared by adapter/connection instances.
56
- module AdapterMethods
57
- attr_writer :db
58
-
59
- SELECT_CURRVAL = "SELECT currval('%s')".freeze
60
- SELECT_CUSTOM_SEQUENCE = proc do |schema, table| <<-end_sql
61
- SELECT '"' || name.nspname || '".' || CASE
62
- WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
63
- substr(split_part(def.adsrc, '''', 2),
64
- strpos(split_part(def.adsrc, '''', 2), '.')+1)
65
- ELSE split_part(def.adsrc, '''', 2)
66
- END
55
+ class CreateTableGenerator < Sequel::Schema::Generator
56
+ # Add an exclusion constraint when creating the table. elements should be
57
+ # an array of 2 element arrays, with the first element being the column or
58
+ # expression the exclusion constraint is applied to, and the second element
59
+ # being the operator to use for the column/expression to check for exclusion.
60
+ #
61
+ # Example:
62
+ #
63
+ # exclusion_constraint([[:col1, '&&'], [:col2, '=']])
64
+ # # EXCLUDE USING gist (col1 WITH &&, col2 WITH =)
65
+ #
66
+ # Options supported:
67
+ #
68
+ # :name :: Name the constraint with the given name (useful if you may
69
+ # need to drop the constraint later)
70
+ # :using :: Override the index_method for the exclusion constraint (defaults to gist).
71
+ # :where :: Create a partial exclusion constraint, which only affects
72
+ # a subset of table rows, value should be a filter expression.
73
+ def exclude(elements, opts={})
74
+ constraints << {:type => :exclude, :elements => elements}.merge(opts)
75
+ end
76
+ end
77
+
78
+ class AlterTableGenerator < Sequel::Schema::AlterTableGenerator
79
+ # Adds an exclusion constraint to an existing table, see
80
+ # CreateTableGenerator#exclude.
81
+ def add_exclusion_constraint(elements, opts={})
82
+ @operations << {:op => :add_constraint, :type => :exclude, :elements => elements}.merge(opts)
83
+ end
84
+
85
+ # Validate the constraint with the given name, which should have
86
+ # been added previously with NOT VALID.
87
+ def validate_constraint(name)
88
+ @operations << {:op => :validate_constraint, :name => name}
89
+ end
90
+ end
91
+
92
+ # Methods shared by Database instances that connect to PostgreSQL.
93
+ module DatabaseMethods
94
+ EXCLUDE_SCHEMAS = /pg_*|information_schema/i
95
+ PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
96
+ RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/.freeze
97
+ SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
98
+ FOREIGN_KEY_LIST_ON_DELETE_MAP = {'a'.freeze=>:no_action, 'r'.freeze=>:restrict, 'c'.freeze=>:cascade, 'n'.freeze=>:set_null, 'd'.freeze=>:set_default}.freeze
99
+ POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
100
+ UNLOGGED = 'UNLOGGED '.freeze
101
+
102
+ # SQL fragment for custom sequences (ones not created by serial primary key),
103
+ # Returning the schema and literal form of the sequence name, by parsing
104
+ # the column defaults table.
105
+ SELECT_CUSTOM_SEQUENCE_SQL = (<<-end_sql
106
+ SELECT name.nspname AS "schema",
107
+ CASE
108
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
109
+ substr(split_part(def.adsrc, '''', 2),
110
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
111
+ ELSE split_part(def.adsrc, '''', 2)
112
+ END AS "sequence"
67
113
  FROM pg_class t
68
114
  JOIN pg_namespace name ON (t.relnamespace = name.oid)
69
115
  JOIN pg_attribute attr ON (t.oid = attrelid)
@@ -71,26 +117,28 @@ module Sequel
71
117
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
72
118
  WHERE cons.contype = 'p'
73
119
  AND def.adsrc ~* 'nextval'
74
- #{"AND name.nspname = '#{schema}'" if schema}
75
- AND t.relname = '#{table}'
76
120
  end_sql
77
- end
78
- SELECT_PK = proc do |schema, table| <<-end_sql
79
- SELECT pg_attribute.attname
121
+ ).strip.gsub(/\s+/, ' ').freeze
122
+
123
+ # SQL fragment for determining primary key column for the given table. Only
124
+ # returns the first primary key if the table has a composite primary key.
125
+ SELECT_PK_SQL = (<<-end_sql
126
+ SELECT pg_attribute.attname AS pk
80
127
  FROM pg_class, pg_attribute, pg_index, pg_namespace
81
128
  WHERE pg_class.oid = pg_attribute.attrelid
82
129
  AND pg_class.relnamespace = pg_namespace.oid
83
130
  AND pg_class.oid = pg_index.indrelid
84
131
  AND pg_index.indkey[0] = pg_attribute.attnum
85
132
  AND pg_index.indisprimary = 't'
86
- #{"AND pg_namespace.nspname = '#{schema}'" if schema}
87
- AND pg_class.relname = '#{table}'
88
133
  end_sql
89
- end
90
- SELECT_SERIAL_SEQUENCE = proc do |schema, table| <<-end_sql
91
- SELECT '"' || name.nspname || '".' || seq.relname || ''
134
+ ).strip.gsub(/\s+/, ' ').freeze
135
+
136
+ # SQL fragment for getting sequence associated with table's
137
+ # primary key, assuming it was a serial primary key column.
138
+ SELECT_SERIAL_SEQUENCE_SQL = (<<-end_sql
139
+ SELECT name.nspname AS "schema", seq.relname AS "sequence"
92
140
  FROM pg_class seq, pg_attribute attr, pg_depend dep,
93
- pg_namespace name, pg_constraint cons
141
+ pg_namespace name, pg_constraint cons, pg_class t
94
142
  WHERE seq.oid = dep.objid
95
143
  AND seq.relnamespace = name.oid
96
144
  AND seq.relkind = 'S'
@@ -98,69 +146,17 @@ module Sequel
98
146
  AND attr.attnum = dep.refobjsubid
99
147
  AND attr.attrelid = cons.conrelid
100
148
  AND attr.attnum = cons.conkey[1]
149
+ AND attr.attrelid = t.oid
101
150
  AND cons.contype = 'p'
102
- #{"AND name.nspname = '#{schema}'" if schema}
103
- AND seq.relname = '#{table}'
104
151
  end_sql
105
- end
106
-
107
- # Depth of the current transaction on this connection, used
108
- # to implement multi-level transactions with savepoints.
109
- attr_accessor :transaction_depth
110
-
111
- # Apply connection settings for this connection. Currently, turns
112
- # standard_conforming_strings ON if Postgres.force_standard_strings
113
- # is true.
114
- def apply_connection_settings
115
- if Postgres.force_standard_strings
116
- # This setting will only work on PostgreSQL 8.2 or greater
117
- # and we don't know the server version at this point, so
118
- # try it unconditionally and rescue any errors.
119
- execute("SET standard_conforming_strings = ON") rescue nil
120
- end
121
- if cmm = Postgres.client_min_messages
122
- execute("SET client_min_messages = '#{cmm.to_s.upcase}'")
123
- end
124
- end
152
+ ).strip.gsub(/\s+/, ' ').freeze
125
153
 
126
- # Get the last inserted value for the given sequence.
127
- def last_insert_id(sequence)
128
- sql = SELECT_CURRVAL % sequence
129
- execute(sql) do |r|
130
- val = single_value(r)
131
- return val.to_i if val
132
- end
133
- end
134
-
135
- # Get the primary key for the given table.
136
- def primary_key(schema, table)
137
- sql = SELECT_PK[schema, table]
138
- execute(sql) do |r|
139
- return single_value(r)
140
- end
141
- end
142
-
143
- # Get the primary key and sequence for the given table.
144
- def sequence(schema, table)
145
- sql = SELECT_SERIAL_SEQUENCE[schema, table]
146
- execute(sql) do |r|
147
- seq = single_value(r)
148
- return seq if seq
149
- end
150
-
151
- sql = SELECT_CUSTOM_SEQUENCE[schema, table]
152
- execute(sql) do |r|
153
- return single_value(r)
154
- end
155
- end
156
- end
157
-
158
- # Methods shared by Database instances that connect to PostgreSQL.
159
- module DatabaseMethods
160
- EXCLUDE_SCHEMAS = /pg_*|information_schema/i
161
- PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
162
- RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/.freeze
163
- SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
154
+ # The Sequel extensions that require reseting of the conversion procs.
155
+ RESET_PROCS_EXTENSIONS = [:pg_array, :pg_hstore, :pg_inet, :pg_interval, :pg_json, :pg_range].freeze
156
+
157
+ # A hash of conversion procs, keyed by type integer (oid) and
158
+ # having callable values for the conversion proc for that type.
159
+ attr_reader :conversion_procs
164
160
 
165
161
  # Commit an existing prepared transaction with the given transaction
166
162
  # identifier string.
@@ -191,7 +187,7 @@ module Sequel
191
187
  def create_function(name, definition, opts={})
192
188
  self << create_function_sql(name, definition, opts)
193
189
  end
194
-
190
+
195
191
  # Create the procedural language in the database. Arguments:
196
192
  # * name : Name of the procedural language (e.g. plpgsql)
197
193
  # * opts : options hash:
@@ -202,7 +198,13 @@ module Sequel
202
198
  def create_language(name, opts={})
203
199
  self << create_language_sql(name, opts)
204
200
  end
205
-
201
+
202
+ # Create a schema in the database. Arguments:
203
+ # * name : Name of the schema (e.g. admin)
204
+ def create_schema(name)
205
+ self << create_schema_sql(name)
206
+ end
207
+
206
208
  # Create a trigger in the database. Arguments:
207
209
  # * table : the table on which this trigger operates
208
210
  # * name : the name of this trigger
@@ -216,7 +218,7 @@ module Sequel
216
218
  def create_trigger(table, name, function, opts={})
217
219
  self << create_trigger_sql(table, name, function, opts)
218
220
  end
219
-
221
+
220
222
  # PostgreSQL uses the :postgres database type.
221
223
  def database_type
222
224
  :postgres
@@ -231,7 +233,7 @@ module Sequel
231
233
  def drop_function(name, opts={})
232
234
  self << drop_function_sql(name, opts)
233
235
  end
234
-
236
+
235
237
  # Drops a procedural language from the database. Arguments:
236
238
  # * name : name of the procedural language to drop
237
239
  # * opts : options hash:
@@ -240,15 +242,14 @@ module Sequel
240
242
  def drop_language(name, opts={})
241
243
  self << drop_language_sql(name, opts)
242
244
  end
243
-
244
- # Remove the cached entries for primary keys and sequences when dropping a table.
245
- def drop_table(*names)
246
- names.each do |name|
247
- name = quote_schema_table(name)
248
- @primary_keys.delete(name)
249
- @primary_key_sequences.delete(name)
250
- end
251
- super
245
+
246
+ # Drops a schema from the database. Arguments:
247
+ # * name : name of the schema to drop
248
+ # * opts : options hash:
249
+ # * :cascade : Drop all objects in this schema.
250
+ # * :if_exists : Don't raise an error if the schema doesn't exist.
251
+ def drop_schema(name, opts={})
252
+ self << drop_schema_sql(name, opts)
252
253
  end
253
254
 
254
255
  # Drops a trigger from the database. Arguments:
@@ -260,7 +261,76 @@ module Sequel
260
261
  def drop_trigger(table, name, opts={})
261
262
  self << drop_trigger_sql(table, name, opts)
262
263
  end
263
-
264
+
265
+ # If any of the extensions that require reseting the conversion procs
266
+ # is loaded, reset them. This is done here so that if you load
267
+ # multiple pg_* extensions in the same call, the conversion procs are
268
+ # only reset once instead of once for every extension.
269
+ def extension(*exts)
270
+ super
271
+ unless (RESET_PROCS_EXTENSIONS & exts).empty?
272
+ reset_conversion_procs
273
+ end
274
+ end
275
+
276
+ # Return full foreign key information using the pg system tables, including
277
+ # :name, :on_delete, :on_update, and :deferrable entries in the hashes.
278
+ def foreign_key_list(table, opts={})
279
+ m = output_identifier_meth
280
+ im = input_identifier_meth
281
+ schema, table = schema_and_table(table)
282
+ range = 0...32
283
+
284
+ base_ds = metadata_dataset.
285
+ where(:cl__relkind=>'r', :co__contype=>'f', :cl__relname=>im.call(table)).
286
+ from(:pg_constraint___co).
287
+ join(:pg_class___cl, :oid=>:conrelid)
288
+
289
+ # We split the parsing into two separate queries, which are merged manually later.
290
+ # This is because PostgreSQL stores both the referencing and referenced columns in
291
+ # arrays, and I don't know a simple way to not create a cross product, as PostgreSQL
292
+ # doesn't appear to have a function that takes an array and element and gives you
293
+ # the index of that element in the array.
294
+
295
+ ds = base_ds.
296
+ join(:pg_attribute___att, :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, :co__conkey)).
297
+ order(:co__conname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:co__conkey, [x]), x]}, 32, :att__attnum)).
298
+ select(:co__conname___name, :att__attname___column, :co__confupdtype___on_update, :co__confdeltype___on_delete,
299
+ SQL::BooleanExpression.new(:AND, :co__condeferrable, :co__condeferred).as(:deferrable))
300
+
301
+ ref_ds = base_ds.
302
+ join(:pg_class___cl2, :oid=>:co__confrelid).
303
+ join(:pg_attribute___att2, :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, :co__confkey)).
304
+ order(:co__conname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:co__conkey, [x]), x]}, 32, :att2__attnum)).
305
+ select(:co__conname___name, :cl2__relname___table, :att2__attname___refcolumn)
306
+
307
+ # If a schema is given, we only search in that schema, and the returned :table
308
+ # entry is schema qualified as well.
309
+ if schema
310
+ ds = ds.join(:pg_namespace___nsp, :oid=>:cl__relnamespace).
311
+ where(:nsp__nspname=>im.call(schema))
312
+ ref_ds = ref_ds.join(:pg_namespace___nsp2, :oid=>:cl2__relnamespace).
313
+ select_more(:nsp2__nspname___schema)
314
+ end
315
+
316
+ h = {}
317
+ fklod_map = FOREIGN_KEY_LIST_ON_DELETE_MAP
318
+ ds.each do |row|
319
+ if r = h[row[:name]]
320
+ r[:columns] << m.call(row[:column])
321
+ else
322
+ h[row[:name]] = {:name=>m.call(row[:name]), :columns=>[m.call(row[:column])], :on_update=>fklod_map[row[:on_update]], :on_delete=>fklod_map[row[:on_delete]], :deferrable=>row[:deferrable]}
323
+ end
324
+ end
325
+ ref_ds.each do |row|
326
+ r = h[row[:name]]
327
+ r[:table] ||= schema ? SQL::QualifiedIdentifier.new(m.call(row[:schema]), m.call(row[:table])) : m.call(row[:table])
328
+ r[:key] ||= []
329
+ r[:key] << m.call(row[:refcolumn])
330
+ end
331
+ h.values
332
+ end
333
+
264
334
  # Use the pg_* system tables to determine indexes on a table
265
335
  def indexes(table, opts={})
266
336
  m = output_identifier_meth
@@ -273,14 +343,13 @@ module Sequel
273
343
  join(:pg_index___ind, :indrelid=>:oid, im.call(table)=>:relname).
274
344
  join(:pg_class___indc, :oid=>:indexrelid).
275
345
  join(:pg_attribute___att, :attrelid=>:tab__oid, :attnum=>attnums).
276
- filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil).
277
- order(:indc__relname, range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}.case(32, :att__attnum)).
346
+ filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil, :indisvalid=>true).
347
+ order(:indc__relname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}, 32, :att__attnum)).
278
348
  select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column)
279
-
349
+
280
350
  ds.join!(:pg_namespace___nsp, :oid=>:tab__relnamespace, :nspname=>schema.to_s) if schema
281
- ds.filter!(:indisvalid=>true) if server_version >= 80200
282
351
  ds.filter!(:indisready=>true, :indcheckxmin=>false) if server_version >= 80300
283
-
352
+
284
353
  indexes = {}
285
354
  ds.each do |r|
286
355
  i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique]}
@@ -289,40 +358,66 @@ module Sequel
289
358
  indexes
290
359
  end
291
360
 
292
- # Dataset containing all current database locks
361
+ # Dataset containing all current database locks
293
362
  def locks
294
363
  dataset.from(:pg_class).join(:pg_locks, :relation=>:relfilenode).select(:pg_class__relname, Sequel::SQL::ColumnAll.new(:pg_locks))
295
364
  end
296
-
365
+
366
+ # Notifies the given channel. See the PostgreSQL NOTIFY documentation. Options:
367
+ #
368
+ # :payload :: The payload string to use for the NOTIFY statement. Only supported
369
+ # in PostgreSQL 9.0+.
370
+ # :server :: The server to which to send the NOTIFY statement, if the sharding support
371
+ # is being used.
372
+ def notify(channel, opts={})
373
+ execute_ddl("NOTIFY #{dataset.send(:table_ref, channel)}#{", #{literal(opts[:payload].to_s)}" if opts[:payload]}", opts)
374
+ end
375
+
297
376
  # Return primary key for the given table.
298
377
  def primary_key(table, opts={})
299
378
  quoted_table = quote_schema_table(table)
300
- return @primary_keys[quoted_table] if @primary_keys.include?(quoted_table)
301
- @primary_keys[quoted_table] = if conn = opts[:conn]
302
- conn.primary_key(*schema_and_table(table))
303
- else
304
- synchronize(opts[:server]){|con| con.primary_key(*schema_and_table(table))}
305
- end
379
+ Sequel.synchronize{return @primary_keys[quoted_table] if @primary_keys.has_key?(quoted_table)}
380
+ schema, table = schema_and_table(table)
381
+ sql = "#{SELECT_PK_SQL} AND pg_class.relname = #{literal(table)}"
382
+ sql << " AND pg_namespace.nspname = #{literal(schema)}" if schema
383
+ value = fetch(sql).single_value
384
+ Sequel.synchronize{@primary_keys[quoted_table] = value}
306
385
  end
307
-
386
+
308
387
  # Return the sequence providing the default for the primary key for the given table.
309
388
  def primary_key_sequence(table, opts={})
310
389
  quoted_table = quote_schema_table(table)
311
- return @primary_key_sequences[quoted_table] if @primary_key_sequences.include?(quoted_table)
312
- @primary_key_sequences[quoted_table] = if conn = opts[:conn]
313
- conn.sequence(*schema_and_table(table))
390
+ Sequel.synchronize{return @primary_key_sequences[quoted_table] if @primary_key_sequences.has_key?(quoted_table)}
391
+ schema, table = schema_and_table(table)
392
+ table = literal(table)
393
+ sql = "#{SELECT_SERIAL_SEQUENCE_SQL} AND t.relname = #{table}"
394
+ sql << " AND name.nspname = #{literal(schema)}" if schema
395
+ if pks = fetch(sql).single_record
396
+ value = literal(SQL::QualifiedIdentifier.new(pks[:schema], pks[:sequence]))
397
+ Sequel.synchronize{@primary_key_sequences[quoted_table] = value}
314
398
  else
315
- synchronize(opts[:server]){|con| con.sequence(*schema_and_table(table))}
399
+ sql = "#{SELECT_CUSTOM_SEQUENCE_SQL} AND t.relname = #{table}"
400
+ sql << " AND name.nspname = #{literal(schema)}" if schema
401
+ if pks = fetch(sql).single_record
402
+ value = literal(SQL::QualifiedIdentifier.new(pks[:schema], LiteralString.new(pks[:sequence])))
403
+ Sequel.synchronize{@primary_key_sequences[quoted_table] = value}
404
+ end
316
405
  end
317
406
  end
318
407
 
319
- # Reset the primary key sequence for the given table, baseing it on the
408
+ # Reset the database's conversion procs, requires a server query if there
409
+ # any named types.
410
+ def reset_conversion_procs
411
+ @conversion_procs = get_conversion_procs
412
+ end
413
+
414
+ # Reset the primary key sequence for the given table, basing it on the
320
415
  # maximum current value of the table's primary key.
321
416
  def reset_primary_key_sequence(table)
322
- pk = SQL::Identifier.new(primary_key(table))
323
417
  return unless seq = primary_key_sequence(table)
418
+ pk = SQL::Identifier.new(primary_key(table))
324
419
  db = self
325
- seq_ds = db.from(seq.lit)
420
+ seq_ds = db.from(LiteralString.new(seq))
326
421
  get{setval(seq, db[table].select{coalesce(max(pk)+seq_ds.select{:increment_by}, seq_ds.select(:min_value))}, false)}
327
422
  end
328
423
 
@@ -337,7 +432,7 @@ module Sequel
337
432
  def serial_primary_key_options
338
433
  {:primary_key => true, :serial => true, :type=>Integer}
339
434
  end
340
-
435
+
341
436
  # The version of the PostgreSQL server, used for determining capability.
342
437
  def server_version(server=nil)
343
438
  return @server_version if @server_version
@@ -345,12 +440,26 @@ module Sequel
345
440
  (conn.server_version rescue nil) if conn.respond_to?(:server_version)
346
441
  end
347
442
  unless @server_version
348
- m = /PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/.match(fetch('SELECT version()').single_value)
349
- @server_version = (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
443
+ @server_version = if m = /PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/.match(fetch('SELECT version()').single_value)
444
+ (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
445
+ else
446
+ 0
447
+ end
350
448
  end
449
+ warn 'Sequel no longer supports PostgreSQL <8.2, some things may not work' if @server_version < 80200
351
450
  @server_version
352
451
  end
353
-
452
+
453
+ # PostgreSQL supports CREATE TABLE IF NOT EXISTS on 9.1+
454
+ def supports_create_table_if_not_exists?
455
+ server_version >= 90100
456
+ end
457
+
458
+ # PostgreSQL supports DROP TABLE IF EXISTS
459
+ def supports_drop_table_if_exists?
460
+ true
461
+ end
462
+
354
463
  # PostgreSQL supports prepared transactions (two-phase commit) if
355
464
  # max_prepared_transactions is greater than 0.
356
465
  def supports_prepared_transactions?
@@ -368,39 +477,128 @@ module Sequel
368
477
  true
369
478
  end
370
479
 
371
- # Whether the given table exists in the database
480
+ # PostgreSQL supports transaction DDL statements.
481
+ def supports_transactional_ddl?
482
+ true
483
+ end
484
+
485
+ # Array of symbols specifying table names in the current database.
486
+ # The dataset used is yielded to the block if one is provided,
487
+ # otherwise, an array of symbols of table names is returned.
372
488
  #
373
489
  # Options:
374
490
  # * :schema - The schema to search (default_schema by default)
375
491
  # * :server - The server to use
376
- def table_exists?(table, opts={})
377
- im = input_identifier_meth
378
- schema, table = schema_and_table(table)
379
- opts[:schema] ||= schema
380
- tables(opts){|ds| !ds.first(:relname=>im.call(table)).nil?}
492
+ def tables(opts={}, &block)
493
+ pg_class_relname('r', opts, &block)
381
494
  end
382
-
383
- # Array of symbols specifying table names in the current database.
384
- # The dataset used is yielded to the block if one is provided,
385
- # otherwise, an array of symbols of table names is returned.
495
+
496
+ # Check whether the given type name string/symbol (e.g. :hstore) is supported by
497
+ # the database.
498
+ def type_supported?(type)
499
+ @supported_types ||= {}
500
+ @supported_types.fetch(type){@supported_types[type] = (from(:pg_type).filter(:typtype=>'b', :typname=>type.to_s).count > 0)}
501
+ end
502
+
503
+ # Array of symbols specifying view names in the current database.
386
504
  #
387
505
  # Options:
388
506
  # * :schema - The schema to search (default_schema by default)
389
507
  # * :server - The server to use
390
- def tables(opts={})
391
- ds = metadata_dataset.from(:pg_class).filter(:relkind=>'r').select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
392
- ds = filter_schema(ds, opts)
393
- m = output_identifier_meth
394
- block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
508
+ def views(opts={})
509
+ pg_class_relname('v', opts)
395
510
  end
396
511
 
397
512
  private
398
513
 
514
+ # Use a PostgreSQL-specific alter table generator
515
+ def alter_table_generator_class
516
+ Postgres::AlterTableGenerator
517
+ end
518
+
519
+ # Handle :using option for set_column_type op, and the :validate_constraint op.
520
+ def alter_table_op_sql(table, op)
521
+ case op[:op]
522
+ when :set_column_type
523
+ s = super
524
+ if using = op[:using]
525
+ using = Sequel::LiteralString.new(using) if using.is_a?(String)
526
+ s << ' USING '
527
+ s << literal(using)
528
+ end
529
+ s
530
+ when :validate_constraint
531
+ "VALIDATE CONSTRAINT #{quote_identifier(op[:name])}"
532
+ else
533
+ super
534
+ end
535
+ end
536
+
537
+ # If the :synchronous option is given and non-nil, set synchronous_commit
538
+ # appropriately. Valid values for the :synchronous option are true,
539
+ # :on, false, :off, :local, and :remote_write.
540
+ def begin_new_transaction(conn, opts)
541
+ super
542
+ if opts.has_key?(:synchronous)
543
+ case sync = opts[:synchronous]
544
+ when true
545
+ sync = :on
546
+ when false
547
+ sync = :off
548
+ when nil
549
+ return
550
+ end
551
+
552
+ log_connection_execute(conn, "SET LOCAL synchronous_commit = #{sync}")
553
+ end
554
+ end
555
+
556
+ # Handle PostgreSQL specific default format.
557
+ def column_schema_normalize_default(default, type)
558
+ if m = POSTGRES_DEFAULT_RE.match(default)
559
+ default = m[1] || m[2]
560
+ end
561
+ super(default, type)
562
+ end
563
+
399
564
  # If the :prepare option is given and we aren't in a savepoint,
400
565
  # prepare the transaction for a two-phase commit.
401
566
  def commit_transaction(conn, opts={})
402
- if opts[:prepare] && Thread.current[:sequel_transaction_depth] <= 1
403
- log_connection_execute(conn, "PREPARE TRANSACTION #{literal(opts[:prepare])}")
567
+ if (s = opts[:prepare]) && _trans(conn)[:savepoint_level] <= 1
568
+ log_connection_execute(conn, "PREPARE TRANSACTION #{literal(s)}")
569
+ else
570
+ super
571
+ end
572
+ end
573
+
574
+ # PostgreSQL can't combine rename_column operations, and it can combine
575
+ # the custom validate_constraint operation.
576
+ def combinable_alter_table_op?(op)
577
+ (super || op[:op] == :validate_constraint) && op[:op] != :rename_column
578
+ end
579
+
580
+ # The SQL queries to execute when starting a new connection.
581
+ def connection_configuration_sqls
582
+ sqls = []
583
+ sqls << "SET standard_conforming_strings = ON" if Postgres.force_standard_strings
584
+ if cmm = Postgres.client_min_messages
585
+ sqls << "SET client_min_messages = '#{cmm.to_s.upcase}'"
586
+ end
587
+ sqls
588
+ end
589
+
590
+ # Handle exclusion constraints.
591
+ def constraint_definition_sql(constraint)
592
+ case constraint[:type]
593
+ when :exclude
594
+ elements = constraint[:elements].map{|c, op| "#{literal(c)} WITH #{op}"}.join(', ')
595
+ "#{"CONSTRAINT #{quote_identifier(constraint[:name])} " if constraint[:name]}EXCLUDE USING #{constraint[:using]||'gist'} (#{elements})#{" WHERE #{filter_expr(constraint[:where])}" if constraint[:where]}"
596
+ when :foreign_key
597
+ sql = super
598
+ if constraint[:not_valid]
599
+ sql << " NOT VALID"
600
+ end
601
+ sql
404
602
  else
405
603
  super
406
604
  end
@@ -426,39 +624,65 @@ module Sequel
426
624
  AS #{literal(definition.to_s)}#{", #{literal(opts[:link_symbol].to_s)}" if opts[:link_symbol]}
427
625
  END
428
626
  end
429
-
627
+
430
628
  # SQL for creating a procedural language.
431
629
  def create_language_sql(name, opts={})
432
630
  "CREATE#{' OR REPLACE' if opts[:replace] && server_version >= 90000}#{' TRUSTED' if opts[:trusted]} LANGUAGE #{name}#{" HANDLER #{opts[:handler]}" if opts[:handler]}#{" VALIDATOR #{opts[:validator]}" if opts[:validator]}"
433
631
  end
434
-
435
- # SQL for creating a database trigger.
632
+
633
+ # SQL for creating a schema.
634
+ def create_schema_sql(name)
635
+ "CREATE SCHEMA #{quote_identifier(name)}"
636
+ end
637
+
638
+ # DDL statement for creating a table with the given name, columns, and options
639
+ def create_table_prefix_sql(name, options)
640
+ temp_or_unlogged_sql = if options[:temp]
641
+ raise(Error, "can't provide both :temp and :unlogged to create_table") if options[:unlogged]
642
+ temporary_table_sql
643
+ elsif options[:unlogged]
644
+ UNLOGGED
645
+ end
646
+ "CREATE #{temp_or_unlogged_sql}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
647
+ end
648
+
649
+ # Use a PostgreSQL-specific create table generator
650
+ def create_table_generator_class
651
+ Postgres::CreateTableGenerator
652
+ end
653
+
654
+ # SQL for creating a database trigger.
436
655
  def create_trigger_sql(table, name, function, opts={})
437
656
  events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
438
657
  whence = opts[:after] ? 'AFTER' : 'BEFORE'
439
658
  "CREATE TRIGGER #{name} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} ON #{quote_schema_table(table)}#{' FOR EACH ROW' if opts[:each_row]} EXECUTE PROCEDURE #{function}(#{Array(opts[:args]).map{|a| literal(a)}.join(', ')})"
440
659
  end
441
-
660
+
442
661
  # The errors that the main adapters can raise, depends on the adapter being used
443
662
  def database_error_classes
444
663
  CONVERTED_EXCEPTIONS
445
664
  end
446
-
447
- # SQL for dropping a function from the database.
665
+
666
+ # SQL for dropping a function from the database.
448
667
  def drop_function_sql(name, opts={})
449
668
  "DROP FUNCTION#{' IF EXISTS' if opts[:if_exists]} #{name}#{sql_function_args(opts[:args])}#{' CASCADE' if opts[:cascade]}"
450
669
  end
451
670
 
671
+ # Support :if_exists, :cascade, and :concurrently options.
672
+ def drop_index_sql(table, op)
673
+ "DROP INDEX#{' CONCURRENTLY' if op[:concurrently]}#{' IF EXISTS' if op[:if_exists]} #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))}#{' CASCADE' if op[:cascade]}"
674
+ end
675
+
452
676
  # SQL for dropping a procedural language from the database.
453
677
  def drop_language_sql(name, opts={})
454
678
  "DROP LANGUAGE#{' IF EXISTS' if opts[:if_exists]} #{name}#{' CASCADE' if opts[:cascade]}"
455
679
  end
456
680
 
457
- # Always CASCADE the table drop
458
- def drop_table_sql(name)
459
- "DROP TABLE #{quote_schema_table(name)} CASCADE"
681
+ # SQL for dropping a schema from the database.
682
+ def drop_schema_sql(name, opts={})
683
+ "DROP SCHEMA#{' IF EXISTS' if opts[:if_exists]} #{quote_identifier(name)}#{' CASCADE' if opts[:cascade]}"
460
684
  end
461
-
685
+
462
686
  # SQL for dropping a trigger from the database.
463
687
  def drop_trigger_sql(table, name, opts={})
464
688
  "DROP TRIGGER#{' IF EXISTS' if opts[:if_exists]} #{name} ON #{quote_schema_table(table)}#{' CASCADE' if opts[:cascade]}"
@@ -473,12 +697,24 @@ module Sequel
473
697
  ds.exclude(:pg_namespace__nspname=>EXCLUDE_SCHEMAS)
474
698
  end
475
699
  end
476
-
700
+
701
+ # Return a hash with oid keys and callable values, used for converting types.
702
+ def get_conversion_procs
703
+ procs = PG_TYPES.dup
704
+ procs[1184] = procs[1114] = method(:to_application_timestamp)
705
+ unless (pgnt = PG_NAMED_TYPES).empty?
706
+ from(:pg_type).where(:typtype=>'b', :typname=>pgnt.keys.map{|t| t.to_s}).select_map([:oid, :typname]).each do |oid, name|
707
+ procs[oid.to_i] ||= pgnt[name.untaint.to_sym]
708
+ end
709
+ end
710
+ procs
711
+ end
712
+
477
713
  # PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
478
714
  def identifier_input_method_default
479
715
  nil
480
716
  end
481
-
717
+
482
718
  # PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
483
719
  def identifier_output_method_default
484
720
  nil
@@ -488,7 +724,7 @@ module Sequel
488
724
  def index_definition_sql(table_name, index)
489
725
  cols = index[:columns]
490
726
  index_name = index[:name] || default_index_name(table_name, cols)
491
- expr = if o = index[:opclass]
727
+ expr = if o = index[:opclass]
492
728
  "(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
493
729
  else
494
730
  literal(Array(cols))
@@ -499,74 +735,80 @@ module Sequel
499
735
  filter = " WHERE #{filter_expr(filter)}" if filter
500
736
  case index_type
501
737
  when :full_text
502
- expr = "(to_tsvector(#{literal(index[:language] || 'simple')}, #{dataset.send(:full_text_string_join, cols)}))"
738
+ expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
503
739
  index_type = :gin
504
740
  when :spatial
505
741
  index_type = :gist
506
742
  end
507
- "CREATE #{unique}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
743
+ "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
508
744
  end
509
-
510
- # The result of the insert for the given table and values. If values
511
- # is an array, assume the first column is the primary key and return
512
- # that. If values is a hash, lookup the primary key for the table. If
513
- # the primary key is present in the hash, return its value. Otherwise,
514
- # look up the sequence for the table's primary key. If one exists,
515
- # return the last value the of the sequence for the connection.
516
- def insert_result(conn, table, values)
517
- case values
518
- when Hash
519
- return nil unless pk = primary_key(table, :conn=>conn)
520
- if pk and pkv = values[pk.to_sym]
521
- pkv
522
- else
523
- begin
524
- if seq = primary_key_sequence(table, :conn=>conn)
525
- conn.last_insert_id(seq)
526
- end
527
- rescue Exception => e
528
- raise_error(e, :classes=>CONVERTED_EXCEPTIONS) unless RE_CURRVAL_ERROR.match(e.message)
529
- end
530
- end
531
- when Array
532
- values.first
533
- else
534
- nil
535
- end
745
+
746
+ # Setup datastructures shared by all postgres adapters.
747
+ def initialize_postgres_adapter
748
+ @primary_keys = {}
749
+ @primary_key_sequences = {}
750
+ @conversion_procs = PG_TYPES.dup
751
+ reset_conversion_procs
536
752
  end
537
753
 
538
- # Don't log, since logging is done by the underlying connection.
539
- def log_connection_execute(conn, sql)
540
- conn.execute(sql)
754
+ # Backbone of the tables and views support.
755
+ def pg_class_relname(type, opts)
756
+ ds = metadata_dataset.from(:pg_class).filter(:relkind=>type).select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
757
+ ds = filter_schema(ds, opts)
758
+ m = output_identifier_meth
759
+ block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
541
760
  end
542
-
761
+
543
762
  # Use a dollar sign instead of question mark for the argument
544
763
  # placeholder.
545
764
  def prepared_arg_placeholder
546
765
  PREPARED_ARG_PLACEHOLDER
547
766
  end
548
-
767
+
768
+ # Remove the cached entries for primary keys and sequences when a table is
769
+ # changed.
770
+ def remove_cached_schema(table)
771
+ tab = quote_schema_table(table)
772
+ Sequel.synchronize do
773
+ @primary_keys.delete(tab)
774
+ @primary_key_sequences.delete(tab)
775
+ end
776
+ super
777
+ end
778
+
549
779
  # SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
550
780
  # a rename table operation, so speciying a new schema in new_name will not have an effect.
551
781
  def rename_table_sql(name, new_name)
552
782
  "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
553
- end
783
+ end
554
784
 
555
785
  # PostgreSQL's autoincrementing primary keys are of type integer or bigint
556
786
  # using a nextval function call as a default.
557
787
  def schema_autoincrementing_primary_key?(schema)
558
- super and schema[:db_type] =~ /\A(?:integer|bigint)\z/io and schema[:default]=~/\Anextval/io
788
+ super && schema[:default] =~ /\Anextval/io
789
+ end
790
+
791
+ # Recognize PostgreSQL interval type.
792
+ def schema_column_type(db_type)
793
+ case db_type
794
+ when /\Ainterval\z/io
795
+ :interval
796
+ else
797
+ super
798
+ end
559
799
  end
560
800
 
561
801
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
562
802
  def schema_parse_table(table_name, opts)
563
- m = output_identifier_meth
564
- m2 = input_identifier_meth
803
+ m = output_identifier_meth(opts[:dataset])
804
+ m2 = input_identifier_meth(opts[:dataset])
565
805
  ds = metadata_dataset.select(:pg_attribute__attname___name,
806
+ SQL::Cast.new(:pg_attribute__atttypid, :integer).as(:oid),
566
807
  SQL::Function.new(:format_type, :pg_type__oid, :pg_attribute__atttypmod).as(:db_type),
567
808
  SQL::Function.new(:pg_get_expr, :pg_attrdef__adbin, :pg_class__oid).as(:default),
568
809
  SQL::BooleanExpression.new(:NOT, :pg_attribute__attnotnull).as(:allow_null),
569
- SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(:pg_attribute__attnum => SQL::Function.new(:ANY, :pg_index__indkey)), false).as(:primary_key)).
810
+ SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(:pg_attribute__attnum => SQL::Function.new(:ANY, :pg_index__indkey)), false).as(:primary_key),
811
+ :pg_namespace__nspname).
570
812
  from(:pg_class).
571
813
  join(:pg_attribute, :attrelid=>:oid).
572
814
  join(:pg_type, :oid=>:atttypid).
@@ -578,18 +820,46 @@ module Sequel
578
820
  filter(:pg_class__relname=>m2.call(table_name)).
579
821
  order(:pg_attribute__attnum)
580
822
  ds = filter_schema(ds, opts)
823
+ current_schema = nil
581
824
  ds.map do |row|
825
+ sch = row.delete(:nspname)
826
+ if current_schema
827
+ if sch != current_schema
828
+ raise Error, "columns from tables in two separate schema were returned (please specify a schema): #{current_schema.inspect}, #{sch.inspect}"
829
+ end
830
+ else
831
+ current_schema = sch
832
+ end
582
833
  row[:default] = nil if blank_object?(row[:default])
583
834
  row[:type] = schema_column_type(row[:db_type])
584
835
  [m.call(row.delete(:name)), row]
585
836
  end
586
837
  end
587
838
 
839
+ # Set the transaction isolation level on the given connection
840
+ def set_transaction_isolation(conn, opts)
841
+ level = opts.fetch(:isolation, transaction_isolation_level)
842
+ read_only = opts[:read_only]
843
+ deferrable = opts[:deferrable]
844
+ if level || !read_only.nil? || !deferrable.nil?
845
+ sql = "SET TRANSACTION"
846
+ sql << " ISOLATION LEVEL #{Sequel::Database::TRANSACTION_ISOLATION_LEVELS[level]}" if level
847
+ sql << " READ #{read_only ? 'ONLY' : 'WRITE'}" unless read_only.nil?
848
+ sql << " #{'NOT ' unless deferrable}DEFERRABLE" unless deferrable.nil?
849
+ log_connection_execute(conn, sql)
850
+ end
851
+ end
852
+
588
853
  # Turns an array of argument specifiers into an SQL fragment used for function arguments. See create_function_sql.
589
854
  def sql_function_args(args)
590
855
  "(#{Array(args).map{|a| Array(a).reverse.join(' ')}.join(', ')})"
591
856
  end
592
-
857
+
858
+ # PostgreSQL can combine multiple alter table ops into a single query.
859
+ def supports_combining_alter_table_ops?
860
+ true
861
+ end
862
+
593
863
  # Handle bigserial type if :serial option is present
594
864
  def type_literal_generic_bignum(column)
595
865
  column[:serial] ? :bigserial : super
@@ -619,7 +889,7 @@ module Sequel
619
889
  end
620
890
  end
621
891
  end
622
-
892
+
623
893
  # Instance methods for datasets that connect to a PostgreSQL database.
624
894
  module DatasetMethods
625
895
  ACCESS_SHARE = 'ACCESS SHARE'.freeze
@@ -627,107 +897,123 @@ module Sequel
627
897
  BOOL_FALSE = 'false'.freeze
628
898
  BOOL_TRUE = 'true'.freeze
629
899
  COMMA_SEPARATOR = ', '.freeze
630
- DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
900
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from using where returning')
901
+ DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with delete from using where returning')
631
902
  EXCLUSIVE = 'EXCLUSIVE'.freeze
632
903
  EXPLAIN = 'EXPLAIN '.freeze
633
904
  EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
634
905
  FOR_SHARE = ' FOR SHARE'.freeze
906
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert into columns values returning')
907
+ INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with insert into columns values returning')
635
908
  LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
636
909
  NULL = LiteralString.new('NULL').freeze
637
910
  PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
638
911
  QUERY_PLAN = 'QUERY PLAN'.to_sym
639
912
  ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
640
913
  ROW_SHARE = 'ROW SHARE'.freeze
641
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit lock')
642
- SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with distinct columns from join where group having window compounds order limit lock')
914
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit lock')
915
+ SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having window compounds order limit lock')
643
916
  SHARE = 'SHARE'.freeze
644
917
  SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
645
918
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
646
919
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
647
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
648
-
920
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set from where returning')
921
+ UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with update table set from where returning')
922
+ SPACE = Dataset::SPACE
923
+ FROM = Dataset::FROM
924
+ APOS = Dataset::APOS
925
+ APOS_RE = Dataset::APOS_RE
926
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
927
+ PAREN_OPEN = Dataset::PAREN_OPEN
928
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
929
+ COMMA = Dataset::COMMA
930
+ AS = Dataset::AS
931
+ XOR_OP = ' # '.freeze
932
+ CRLF = "\r\n".freeze
933
+ BLOB_RE = /[\000-\037\047\134\177-\377]/n.freeze
934
+ WINDOW = " WINDOW ".freeze
935
+ EMPTY_STRING = ''.freeze
936
+
649
937
  # Shared methods for prepared statements when used with PostgreSQL databases.
650
938
  module PreparedStatementMethods
651
939
  # Override insert action to use RETURNING if the server supports it.
940
+ def run
941
+ if @prepared_type == :insert
942
+ fetch_rows(prepared_sql){|r| return r.values.first}
943
+ else
944
+ super
945
+ end
946
+ end
947
+
652
948
  def prepared_sql
653
949
  return @prepared_sql if @prepared_sql
950
+ @opts[:returning] = insert_pk if @prepared_type == :insert
654
951
  super
655
- if @prepared_type == :insert and !@opts[:disable_insert_returning] and server_version >= 80200
656
- @prepared_sql = insert_returning_pk_sql(*@prepared_modify_values)
657
- meta_def(:insert_returning_pk_sql){|*args| prepared_sql}
658
- end
659
952
  @prepared_sql
660
953
  end
661
954
  end
662
955
 
663
- # Add the disable_insert_returning! mutation method
664
- def self.extended(obj)
665
- obj.def_mutation_method(:disable_insert_returning)
666
- end
667
-
668
- # Add the disable_insert_returning! mutation method
669
- def self.included(mod)
670
- mod.def_mutation_method(:disable_insert_returning)
671
- end
672
-
673
- # Return the results of an ANALYZE query as a string
956
+ # Return the results of an EXPLAIN ANALYZE query as a string
674
957
  def analyze
675
958
  explain(:analyze=>true)
676
959
  end
677
-
960
+
678
961
  # Handle converting the ruby xor operator (^) into the
679
962
  # PostgreSQL xor operator (#).
680
- def complex_expression_sql(op, args)
963
+ def complex_expression_sql_append(sql, op, args)
681
964
  case op
682
965
  when :^
683
- "(#{literal(args.at(0))} # #{literal(args.at(1))})"
966
+ j = XOR_OP
967
+ c = false
968
+ args.each do |a|
969
+ sql << j if c
970
+ literal_append(sql, a)
971
+ c ||= true
972
+ end
684
973
  else
685
974
  super
686
975
  end
687
976
  end
688
977
 
689
- # Disable the use of INSERT RETURNING, even if the server supports it
690
- def disable_insert_returning
691
- clone(:disable_insert_returning=>true)
692
- end
693
-
694
978
  # Return the results of an EXPLAIN query as a string
695
979
  def explain(opts={})
696
- with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join("\r\n")
980
+ with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(CRLF)
697
981
  end
698
-
982
+
699
983
  # Return a cloned dataset which will use FOR SHARE to lock returned rows.
700
984
  def for_share
701
985
  lock_style(:share)
702
986
  end
703
-
987
+
704
988
  # PostgreSQL specific full text search syntax, using tsearch2 (included
705
989
  # in 8.3 by default, and available for earlier versions as an add-on).
706
990
  def full_text_search(cols, terms, opts = {})
707
991
  lang = opts[:language] || 'simple'
708
- filter("to_tsvector(#{literal(lang)}, #{full_text_string_join(cols)}) @@ to_tsquery(#{literal(lang)}, #{literal(Array(terms).join(' | '))})")
992
+ terms = terms.join(' | ') if terms.is_a?(Array)
993
+ filter("to_tsvector(?::regconfig, ?) @@ to_tsquery(?::regconfig, ?)", lang, full_text_string_join(cols), lang, terms)
709
994
  end
710
-
995
+
711
996
  # Insert given values into the database.
712
997
  def insert(*values)
713
- if @opts[:sql]
714
- execute_insert(insert_sql(*values))
715
- elsif @opts[:disable_insert_returning] || server_version < 80200
716
- execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
998
+ if @opts[:returning]
999
+ # already know which columns to return, let the standard code
1000
+ # handle it
1001
+ super
1002
+ elsif @opts[:sql]
1003
+ # raw SQL used, so don't know which table is being inserted
1004
+ # into, and therefore can't determine primary key. Run the
1005
+ # insert statement and return nil.
1006
+ super
1007
+ nil
717
1008
  else
718
- clone(default_server_opts(:sql=>insert_returning_pk_sql(*values))).single_value
1009
+ # Force the use of RETURNING with the primary key value.
1010
+ returning(insert_pk).insert(*values){|r| return r.values.first}
719
1011
  end
720
1012
  end
721
1013
 
722
- # Use the RETURNING clause to return the columns listed in returning.
723
- def insert_returning_sql(returning, *values)
724
- "#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
725
- end
726
-
727
1014
  # Insert a record returning the record inserted
728
1015
  def insert_select(*values)
729
- return if opts[:disable_insert_returning] || server_version < 80200
730
- naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
1016
+ returning.insert(*values){|r| return r}
731
1017
  end
732
1018
 
733
1019
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
@@ -743,50 +1029,124 @@ module Sequel
743
1029
  end
744
1030
  nil
745
1031
  end
746
-
747
- # For PostgreSQL version > 8.2, allow inserting multiple rows at once.
1032
+
1033
+ # PostgreSQL allows inserting multiple rows at once.
748
1034
  def multi_insert_sql(columns, values)
749
- return super if server_version < 80200
750
-
751
- # postgresql 8.2 introduces support for multi-row insert
752
- [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
1035
+ sql = LiteralString.new('VALUES ')
1036
+ expression_list_append(sql, values.map{|r| Array(r)})
1037
+ [insert_sql(columns, sql)]
753
1038
  end
754
-
1039
+
1040
+ # PostgreSQL supports using the WITH clause in subqueries if it
1041
+ # supports using WITH at all (i.e. on PostgreSQL 8.4+).
1042
+ def supports_cte_in_subqueries?
1043
+ supports_cte?
1044
+ end
1045
+
755
1046
  # DISTINCT ON is a PostgreSQL extension
756
1047
  def supports_distinct_on?
757
1048
  true
758
1049
  end
759
-
1050
+
760
1051
  # PostgreSQL supports modifying joined datasets
761
1052
  def supports_modifying_joins?
762
1053
  true
763
1054
  end
764
1055
 
1056
+ # Returning is always supported.
1057
+ def supports_returning?(type)
1058
+ true
1059
+ end
1060
+
1061
+ # PostgreSQL supports pattern matching via regular expressions
1062
+ def supports_regexp?
1063
+ true
1064
+ end
1065
+
765
1066
  # PostgreSQL supports timezones in literal timestamps
766
1067
  def supports_timestamp_timezones?
767
1068
  true
768
1069
  end
769
-
1070
+
770
1071
  # PostgreSQL 8.4+ supports window functions
771
1072
  def supports_window_functions?
772
1073
  server_version >= 80400
773
1074
  end
774
1075
 
1076
+ # Truncates the dataset. Returns nil.
1077
+ #
1078
+ # Options:
1079
+ # :cascade :: whether to use the CASCADE option, useful when truncating
1080
+ # tables with Foreign Keys.
1081
+ # :only :: truncate using ONLY, so child tables are unaffected
1082
+ # :restart :: use RESTART IDENTITY to restart any related sequences
1083
+ #
1084
+ # :only and :restart only work correctly on PostgreSQL 8.4+.
1085
+ #
1086
+ # Usage:
1087
+ # DB[:table].truncate # TRUNCATE TABLE "table"
1088
+ # # => nil
1089
+ # DB[:table].truncate(:cascade => true, :only=>true, :restart=>true) # TRUNCATE TABLE ONLY "table" RESTART IDENTITY CASCADE
1090
+ # # => nil
1091
+ def truncate(opts = {})
1092
+ if opts.empty?
1093
+ super()
1094
+ else
1095
+ clone(:truncate_opts=>opts).truncate
1096
+ end
1097
+ end
1098
+
775
1099
  # Return a clone of the dataset with an addition named window that can be referenced in window functions.
776
1100
  def window(name, opts)
777
1101
  clone(:window=>(@opts[:window]||[]) + [[name, SQL::Window.new(opts)]])
778
1102
  end
779
-
1103
+
1104
+ protected
1105
+
1106
+ # If returned primary keys are requested, use RETURNING unless already set on the
1107
+ # dataset. If RETURNING is already set, use existing returning values. If RETURNING
1108
+ # is only set to return a single columns, return an array of just that column.
1109
+ # Otherwise, return an array of hashes.
1110
+ def _import(columns, values, opts={})
1111
+ if @opts[:returning]
1112
+ statements = multi_insert_sql(columns, values)
1113
+ @db.transaction(opts.merge(:server=>@opts[:server])) do
1114
+ statements.map{|st| returning_fetch_rows(st)}
1115
+ end.first.map{|v| v.length == 1 ? v.values.first : v}
1116
+ elsif opts[:return] == :primary_key
1117
+ returning(insert_pk)._import(columns, values, opts)
1118
+ else
1119
+ super
1120
+ end
1121
+ end
1122
+
780
1123
  private
781
-
1124
+
1125
+ # Format TRUNCATE statement with PostgreSQL specific options.
1126
+ def _truncate_sql(table)
1127
+ to = @opts[:truncate_opts] || {}
1128
+ "TRUNCATE TABLE#{' ONLY' if to[:only]} #{table}#{' RESTART IDENTITY' if to[:restart]}#{' CASCADE' if to[:cascade]}"
1129
+ end
1130
+
1131
+ # Allow truncation of multiple source tables.
1132
+ def check_truncation_allowed!
1133
+ raise(InvalidOperation, "Grouped datasets cannot be truncated") if opts[:group]
1134
+ raise(InvalidOperation, "Joined datasets cannot be truncated") if opts[:join]
1135
+ end
1136
+
782
1137
  # PostgreSQL allows deleting from joined datasets
783
1138
  def delete_clause_methods
784
- DELETE_CLAUSE_METHODS
785
- end
1139
+ if server_version >= 90100
1140
+ DELETE_CLAUSE_METHODS_91
1141
+ else
1142
+ DELETE_CLAUSE_METHODS
1143
+ end
1144
+ end
786
1145
 
787
1146
  # Only include the primary table in the main delete clause
788
1147
  def delete_from_sql(sql)
789
- sql << " FROM #{source_list(@opts[:from][0..0])}"
1148
+ sql << FROM
1149
+ source_list_append(sql, @opts[:from][0..0])
790
1150
  end
791
1151
 
792
1152
  # Use USING to specify additional tables in a delete query
@@ -794,36 +1154,60 @@ module Sequel
794
1154
  join_from_sql(:USING, sql)
795
1155
  end
796
1156
 
797
- # Use the RETURNING clause to return the primary key of the inserted record, if it exists
798
- def insert_returning_pk_sql(*values)
799
- pk = db.primary_key(opts[:from].first) if opts[:from] && !opts[:from].empty?
800
- insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : NULL, *values)
1157
+ # PostgreSQL allows a RETURNING clause.
1158
+ def insert_clause_methods
1159
+ if server_version >= 90100
1160
+ INSERT_CLAUSE_METHODS_91
1161
+ else
1162
+ INSERT_CLAUSE_METHODS
1163
+ end
801
1164
  end
802
-
1165
+
1166
+ # Return the primary key to use for RETURNING in an INSERT statement
1167
+ def insert_pk
1168
+ if (f = opts[:from]) && !f.empty? && (pk = db.primary_key(f.first))
1169
+ Sequel::SQL::Identifier.new(pk)
1170
+ end
1171
+ end
1172
+
803
1173
  # For multiple table support, PostgreSQL requires at least
804
1174
  # two from tables, with joins allowed.
805
1175
  def join_from_sql(type, sql)
806
1176
  if(from = @opts[:from][1..-1]).empty?
807
1177
  raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
808
1178
  else
809
- sql << " #{type} #{source_list(from)}"
1179
+ sql << SPACE << type.to_s << SPACE
1180
+ source_list_append(sql, from)
810
1181
  select_join_sql(sql)
811
1182
  end
812
1183
  end
813
1184
 
814
1185
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
815
- def literal_blob(v)
816
- "'#{v.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
1186
+ def literal_blob_append(sql, v)
1187
+ sql << APOS << v.gsub(BLOB_RE){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"} << APOS
817
1188
  end
818
1189
 
819
1190
  # PostgreSQL uses FALSE for false values
820
1191
  def literal_false
821
1192
  BOOL_FALSE
822
1193
  end
1194
+
1195
+ # PostgreSQL quotes NaN and Infinity.
1196
+ def literal_float(value)
1197
+ if value.finite?
1198
+ super
1199
+ elsif value.nan?
1200
+ "'NaN'"
1201
+ elsif value.infinite? == 1
1202
+ "'Infinity'"
1203
+ else
1204
+ "'-Infinity'"
1205
+ end
1206
+ end
823
1207
 
824
1208
  # Assume that SQL standard quoting is on, per Sequel's defaults
825
- def literal_string(v)
826
- "'#{v.gsub("'", "''")}'"
1209
+ def literal_string_append(sql, v)
1210
+ sql << APOS << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
827
1211
  end
828
1212
 
829
1213
  # PostgreSQL uses FALSE for false values
@@ -835,7 +1219,15 @@ module Sequel
835
1219
  def select_clause_methods
836
1220
  server_version >= 80400 ? SELECT_CLAUSE_METHODS_84 : SELECT_CLAUSE_METHODS
837
1221
  end
838
-
1222
+
1223
+ # PostgreSQL requires parentheses around compound datasets if they use
1224
+ # CTEs, and using them in other places doesn't hurt.
1225
+ def compound_dataset_sql_append(sql, ds)
1226
+ sql << PAREN_OPEN
1227
+ super
1228
+ sql << PAREN_CLOSE
1229
+ end
1230
+
839
1231
  # Support FOR SHARE locking when using the :share lock style.
840
1232
  def select_lock_sql(sql)
841
1233
  @opts[:lock] == :share ? (sql << FOR_SHARE) : super
@@ -843,14 +1235,26 @@ module Sequel
843
1235
 
844
1236
  # SQL fragment for named window specifications
845
1237
  def select_window_sql(sql)
846
- sql << " WINDOW #{@opts[:window].map{|name, window| "#{literal(name)} AS #{literal(window)}"}.join(', ')}" if @opts[:window]
1238
+ if ws = @opts[:window]
1239
+ sql << WINDOW
1240
+ c = false
1241
+ co = COMMA
1242
+ as = AS
1243
+ ws.map do |name, window|
1244
+ sql << co if c
1245
+ literal_append(sql, name)
1246
+ sql << as
1247
+ literal_append(sql, window)
1248
+ c ||= true
1249
+ end
1250
+ end
847
1251
  end
848
-
1252
+
849
1253
  # Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
850
1254
  def select_with_sql_base
851
1255
  opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
852
1256
  end
853
-
1257
+
854
1258
  # The version of the database server
855
1259
  def server_version
856
1260
  db.server_version(@opts[:server])
@@ -858,15 +1262,19 @@ module Sequel
858
1262
 
859
1263
  # Concatenate the expressions with a space in between
860
1264
  def full_text_string_join(cols)
861
- cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, '')}
862
- cols = cols.zip([' '] * cols.length).flatten
1265
+ cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, EMPTY_STRING)}
1266
+ cols = cols.zip([SPACE] * cols.length).flatten
863
1267
  cols.pop
864
- literal(SQL::StringExpression.new(:'||', *cols))
1268
+ SQL::StringExpression.new(:'||', *cols)
865
1269
  end
866
1270
 
867
1271
  # PostgreSQL splits the main table from the joined tables
868
1272
  def update_clause_methods
869
- UPDATE_CLAUSE_METHODS
1273
+ if server_version >= 90100
1274
+ UPDATE_CLAUSE_METHODS_91
1275
+ else
1276
+ UPDATE_CLAUSE_METHODS
1277
+ end
870
1278
  end
871
1279
 
872
1280
  # Use FROM to specify additional tables in an update query
@@ -876,7 +1284,8 @@ module Sequel
876
1284
 
877
1285
  # Only include the primary table in the main update clause
878
1286
  def update_table_sql(sql)
879
- sql << " #{source_list(@opts[:from][0..0])}"
1287
+ sql << SPACE
1288
+ source_list_append(sql, @opts[:from][0..0])
880
1289
  end
881
1290
  end
882
1291
  end