sequel 3.21.0 → 3.39.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 (352) hide show
  1. data/CHANGELOG +1061 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +103 -63
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +67 -51
  7. data/doc/advanced_associations.rdoc +189 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/cheat_sheet.rdoc +21 -21
  10. data/doc/core_extensions.rdoc +374 -0
  11. data/doc/dataset_basics.rdoc +10 -10
  12. data/doc/dataset_filtering.rdoc +54 -42
  13. data/doc/mass_assignment.rdoc +56 -0
  14. data/doc/migration.rdoc +89 -557
  15. data/doc/model_hooks.rdoc +80 -29
  16. data/doc/object_model.rdoc +547 -0
  17. data/doc/opening_databases.rdoc +114 -53
  18. data/doc/prepared_statements.rdoc +41 -15
  19. data/doc/querying.rdoc +137 -56
  20. data/doc/reflection.rdoc +36 -10
  21. data/doc/release_notes/3.22.0.txt +39 -0
  22. data/doc/release_notes/3.23.0.txt +172 -0
  23. data/doc/release_notes/3.24.0.txt +420 -0
  24. data/doc/release_notes/3.25.0.txt +88 -0
  25. data/doc/release_notes/3.26.0.txt +88 -0
  26. data/doc/release_notes/3.27.0.txt +82 -0
  27. data/doc/release_notes/3.28.0.txt +304 -0
  28. data/doc/release_notes/3.29.0.txt +459 -0
  29. data/doc/release_notes/3.30.0.txt +135 -0
  30. data/doc/release_notes/3.31.0.txt +146 -0
  31. data/doc/release_notes/3.32.0.txt +202 -0
  32. data/doc/release_notes/3.33.0.txt +157 -0
  33. data/doc/release_notes/3.34.0.txt +671 -0
  34. data/doc/release_notes/3.35.0.txt +144 -0
  35. data/doc/release_notes/3.36.0.txt +245 -0
  36. data/doc/release_notes/3.37.0.txt +338 -0
  37. data/doc/release_notes/3.38.0.txt +234 -0
  38. data/doc/release_notes/3.39.0.txt +237 -0
  39. data/doc/schema_modification.rdoc +585 -0
  40. data/doc/sharding.rdoc +99 -8
  41. data/doc/sql.rdoc +154 -112
  42. data/doc/testing.rdoc +169 -0
  43. data/doc/thread_safety.rdoc +17 -0
  44. data/doc/transactions.rdoc +137 -0
  45. data/doc/validations.rdoc +1 -1
  46. data/doc/virtual_rows.rdoc +78 -43
  47. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  48. data/lib/sequel/adapters/ado.rb +40 -18
  49. data/lib/sequel/adapters/amalgalite.rb +17 -9
  50. data/lib/sequel/adapters/db2.rb +175 -86
  51. data/lib/sequel/adapters/dbi.rb +15 -15
  52. data/lib/sequel/adapters/do/mysql.rb +17 -11
  53. data/lib/sequel/adapters/do/postgres.rb +2 -61
  54. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  55. data/lib/sequel/adapters/do.rb +18 -36
  56. data/lib/sequel/adapters/firebird.rb +27 -208
  57. data/lib/sequel/adapters/ibmdb.rb +453 -0
  58. data/lib/sequel/adapters/informix.rb +6 -23
  59. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  60. data/lib/sequel/adapters/jdbc/db2.rb +56 -0
  61. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  62. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  63. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  64. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  65. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  66. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  67. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  68. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  69. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  70. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  71. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  73. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  74. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  75. data/lib/sequel/adapters/jdbc.rb +279 -97
  76. data/lib/sequel/adapters/mock.rb +372 -0
  77. data/lib/sequel/adapters/mysql.rb +125 -222
  78. data/lib/sequel/adapters/mysql2.rb +65 -56
  79. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  80. data/lib/sequel/adapters/odbc.rb +28 -9
  81. data/lib/sequel/adapters/openbase.rb +3 -5
  82. data/lib/sequel/adapters/oracle.rb +349 -53
  83. data/lib/sequel/adapters/postgres.rb +368 -138
  84. data/lib/sequel/adapters/shared/access.rb +32 -9
  85. data/lib/sequel/adapters/shared/db2.rb +343 -0
  86. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  87. data/lib/sequel/adapters/shared/informix.rb +53 -0
  88. data/lib/sequel/adapters/shared/mssql.rb +401 -116
  89. data/lib/sequel/adapters/shared/mysql.rb +424 -89
  90. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  91. data/lib/sequel/adapters/shared/oracle.rb +257 -45
  92. data/lib/sequel/adapters/shared/postgres.rb +675 -279
  93. data/lib/sequel/adapters/shared/progress.rb +5 -8
  94. data/lib/sequel/adapters/shared/sqlite.rb +285 -96
  95. data/lib/sequel/adapters/sqlite.rb +101 -39
  96. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  97. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  98. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  99. data/lib/sequel/adapters/swift.rb +28 -19
  100. data/lib/sequel/adapters/tinytds.rb +174 -22
  101. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  102. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  103. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  104. data/lib/sequel/ast_transformer.rb +194 -0
  105. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  106. data/lib/sequel/connection_pool/sharded_threaded.rb +33 -2
  107. data/lib/sequel/connection_pool/single.rb +5 -0
  108. data/lib/sequel/connection_pool/threaded.rb +43 -14
  109. data/lib/sequel/connection_pool.rb +10 -2
  110. data/lib/sequel/core.rb +143 -11
  111. data/lib/sequel/database/connecting.rb +38 -28
  112. data/lib/sequel/database/dataset.rb +4 -4
  113. data/lib/sequel/database/dataset_defaults.rb +63 -2
  114. data/lib/sequel/database/logging.rb +7 -2
  115. data/lib/sequel/database/misc.rb +190 -46
  116. data/lib/sequel/database/query.rb +272 -110
  117. data/lib/sequel/database/schema_generator.rb +103 -36
  118. data/lib/sequel/database/schema_methods.rb +300 -67
  119. data/lib/sequel/dataset/actions.rb +344 -90
  120. data/lib/sequel/dataset/features.rb +101 -8
  121. data/lib/sequel/dataset/graph.rb +42 -15
  122. data/lib/sequel/dataset/misc.rb +52 -25
  123. data/lib/sequel/dataset/mutation.rb +12 -7
  124. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  125. data/lib/sequel/dataset/query.rb +305 -54
  126. data/lib/sequel/dataset/sql.rb +720 -292
  127. data/lib/sequel/dataset.rb +8 -0
  128. data/lib/sequel/exceptions.rb +4 -0
  129. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  130. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  131. data/lib/sequel/extensions/blank.rb +4 -0
  132. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  133. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  134. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  135. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  136. data/lib/sequel/extensions/inflector.rb +4 -0
  137. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  138. data/lib/sequel/extensions/migration.rb +100 -11
  139. data/lib/sequel/extensions/named_timezones.rb +9 -0
  140. data/lib/sequel/extensions/null_dataset.rb +94 -0
  141. data/lib/sequel/extensions/pagination.rb +4 -0
  142. data/lib/sequel/extensions/pg_array.rb +537 -0
  143. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  144. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  145. data/lib/sequel/extensions/pg_hstore.rb +313 -0
  146. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  147. data/lib/sequel/extensions/pg_inet.rb +113 -0
  148. data/lib/sequel/extensions/pg_interval.rb +191 -0
  149. data/lib/sequel/extensions/pg_json.rb +208 -0
  150. data/lib/sequel/extensions/pg_range.rb +512 -0
  151. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  152. data/lib/sequel/extensions/pg_row.rb +572 -0
  153. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  154. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  155. data/lib/sequel/extensions/pretty_table.rb +17 -72
  156. data/lib/sequel/extensions/query.rb +8 -4
  157. data/lib/sequel/extensions/query_literals.rb +79 -0
  158. data/lib/sequel/extensions/schema_caching.rb +76 -0
  159. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  160. data/lib/sequel/extensions/select_remove.rb +39 -0
  161. data/lib/sequel/extensions/server_block.rb +140 -0
  162. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  163. data/lib/sequel/extensions/sql_expr.rb +8 -110
  164. data/lib/sequel/extensions/string_date_time.rb +4 -0
  165. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  166. data/lib/sequel/extensions/to_dot.rb +99 -83
  167. data/lib/sequel/model/associations.rb +1263 -409
  168. data/lib/sequel/model/base.rb +624 -171
  169. data/lib/sequel/model/errors.rb +1 -1
  170. data/lib/sequel/model/exceptions.rb +24 -2
  171. data/lib/sequel/model/inflections.rb +1 -1
  172. data/lib/sequel/model.rb +30 -11
  173. data/lib/sequel/no_core_ext.rb +2 -0
  174. data/lib/sequel/plugins/active_model.rb +13 -1
  175. data/lib/sequel/plugins/association_pks.rb +22 -4
  176. data/lib/sequel/plugins/caching.rb +25 -18
  177. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  178. data/lib/sequel/plugins/composition.rb +44 -12
  179. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  180. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  181. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  182. data/lib/sequel/plugins/dirty.rb +214 -0
  183. data/lib/sequel/plugins/eager_each.rb +59 -0
  184. data/lib/sequel/plugins/force_encoding.rb +6 -6
  185. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  186. data/lib/sequel/plugins/identity_map.rb +134 -15
  187. data/lib/sequel/plugins/instance_filters.rb +10 -0
  188. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  189. data/lib/sequel/plugins/json_serializer.rb +58 -6
  190. data/lib/sequel/plugins/list.rb +13 -2
  191. data/lib/sequel/plugins/many_through_many.rb +103 -51
  192. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  193. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  194. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  195. data/lib/sequel/plugins/pg_row.rb +121 -0
  196. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  197. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  198. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  199. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  200. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  201. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  202. data/lib/sequel/plugins/schema.rb +8 -3
  203. data/lib/sequel/plugins/serialization.rb +98 -49
  204. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  205. data/lib/sequel/plugins/sharding.rb +21 -54
  206. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  207. data/lib/sequel/plugins/static_cache.rb +99 -0
  208. data/lib/sequel/plugins/subclasses.rb +29 -3
  209. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  210. data/lib/sequel/plugins/timestamps.rb +1 -1
  211. data/lib/sequel/plugins/tree.rb +3 -3
  212. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  213. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  214. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  215. data/lib/sequel/plugins/validation_helpers.rb +55 -4
  216. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  217. data/lib/sequel/sql.rb +649 -122
  218. data/lib/sequel/timezones.rb +67 -40
  219. data/lib/sequel/version.rb +1 -1
  220. data/spec/adapters/db2_spec.rb +146 -0
  221. data/spec/adapters/firebird_spec.rb +1 -1
  222. data/spec/adapters/mssql_spec.rb +194 -66
  223. data/spec/adapters/mysql_spec.rb +475 -306
  224. data/spec/adapters/oracle_spec.rb +92 -117
  225. data/spec/adapters/postgres_spec.rb +1982 -325
  226. data/spec/adapters/spec_helper.rb +9 -6
  227. data/spec/adapters/sqlite_spec.rb +203 -75
  228. data/spec/core/connection_pool_spec.rb +218 -93
  229. data/spec/core/database_spec.rb +956 -522
  230. data/spec/core/dataset_spec.rb +1631 -1242
  231. data/spec/core/expression_filters_spec.rb +690 -334
  232. data/spec/core/mock_adapter_spec.rb +453 -0
  233. data/spec/core/object_graph_spec.rb +88 -129
  234. data/spec/core/schema_generator_spec.rb +4 -4
  235. data/spec/core/schema_spec.rb +556 -53
  236. data/spec/core/spec_helper.rb +7 -48
  237. data/spec/core_extensions_spec.rb +626 -0
  238. data/spec/extensions/active_model_spec.rb +13 -0
  239. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  240. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  241. data/spec/extensions/association_dependencies_spec.rb +15 -25
  242. data/spec/extensions/association_pks_spec.rb +66 -32
  243. data/spec/extensions/association_proxies_spec.rb +4 -4
  244. data/spec/extensions/boolean_readers_spec.rb +25 -25
  245. data/spec/extensions/caching_spec.rb +47 -51
  246. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  247. data/spec/extensions/columns_introspection_spec.rb +91 -0
  248. data/spec/extensions/composition_spec.rb +18 -13
  249. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  250. data/spec/extensions/constraint_validations_spec.rb +316 -0
  251. data/spec/extensions/dataset_associations_spec.rb +199 -0
  252. data/spec/extensions/defaults_setter_spec.rb +88 -0
  253. data/spec/extensions/dirty_spec.rb +155 -0
  254. data/spec/extensions/eager_each_spec.rb +34 -0
  255. data/spec/extensions/eval_inspect_spec.rb +67 -0
  256. data/spec/extensions/force_encoding_spec.rb +4 -2
  257. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  258. data/spec/extensions/identity_map_spec.rb +142 -24
  259. data/spec/extensions/inflector_spec.rb +0 -4
  260. data/spec/extensions/instance_filters_spec.rb +11 -21
  261. data/spec/extensions/instance_hooks_spec.rb +72 -0
  262. data/spec/extensions/json_serializer_spec.rb +49 -12
  263. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  264. data/spec/extensions/list_spec.rb +49 -40
  265. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  266. data/spec/extensions/many_through_many_spec.rb +368 -254
  267. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  268. data/spec/extensions/migration_spec.rb +140 -35
  269. data/spec/extensions/named_timezones_spec.rb +29 -11
  270. data/spec/extensions/nested_attributes_spec.rb +268 -89
  271. data/spec/extensions/null_dataset_spec.rb +85 -0
  272. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  273. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  274. data/spec/extensions/pg_array_spec.rb +320 -0
  275. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  276. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  277. data/spec/extensions/pg_hstore_spec.rb +194 -0
  278. data/spec/extensions/pg_inet_spec.rb +47 -0
  279. data/spec/extensions/pg_interval_spec.rb +72 -0
  280. data/spec/extensions/pg_json_spec.rb +99 -0
  281. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  282. data/spec/extensions/pg_range_spec.rb +395 -0
  283. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  284. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  285. data/spec/extensions/pg_row_spec.rb +323 -0
  286. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  287. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  288. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  289. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  290. data/spec/extensions/prepared_statements_spec.rb +87 -0
  291. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  292. data/spec/extensions/pretty_table_spec.rb +7 -6
  293. data/spec/extensions/query_literals_spec.rb +167 -0
  294. data/spec/extensions/query_spec.rb +9 -3
  295. data/spec/extensions/rcte_tree_spec.rb +50 -43
  296. data/spec/extensions/schema_caching_spec.rb +41 -0
  297. data/spec/extensions/schema_dumper_spec.rb +434 -49
  298. data/spec/extensions/schema_spec.rb +30 -49
  299. data/spec/extensions/select_remove_spec.rb +38 -0
  300. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  301. data/spec/extensions/serialization_spec.rb +86 -48
  302. data/spec/extensions/server_block_spec.rb +90 -0
  303. data/spec/extensions/sharding_spec.rb +69 -143
  304. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  305. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  306. data/spec/extensions/spec_helper.rb +34 -67
  307. data/spec/extensions/split_array_nil_spec.rb +24 -0
  308. data/spec/extensions/sql_expr_spec.rb +29 -60
  309. data/spec/extensions/static_cache_spec.rb +145 -0
  310. data/spec/extensions/subclasses_spec.rb +14 -0
  311. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  312. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  313. data/spec/extensions/timestamps_spec.rb +6 -6
  314. data/spec/extensions/to_dot_spec.rb +8 -11
  315. data/spec/extensions/touch_spec.rb +13 -14
  316. data/spec/extensions/tree_spec.rb +11 -26
  317. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  318. data/spec/extensions/update_primary_key_spec.rb +30 -24
  319. data/spec/extensions/validation_class_methods_spec.rb +58 -67
  320. data/spec/extensions/validation_helpers_spec.rb +31 -37
  321. data/spec/extensions/xml_serializer_spec.rb +29 -4
  322. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  323. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  324. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  325. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  326. data/spec/integration/associations_test.rb +659 -21
  327. data/spec/integration/database_test.rb +11 -5
  328. data/spec/integration/dataset_test.rb +717 -129
  329. data/spec/integration/eager_loader_test.rb +38 -54
  330. data/spec/integration/migrator_test.rb +2 -3
  331. data/spec/integration/model_test.rb +81 -2
  332. data/spec/integration/plugin_test.rb +604 -147
  333. data/spec/integration/prepared_statement_test.rb +272 -128
  334. data/spec/integration/schema_test.rb +367 -57
  335. data/spec/integration/spec_helper.rb +42 -32
  336. data/spec/integration/timezone_test.rb +38 -12
  337. data/spec/integration/transaction_test.rb +183 -8
  338. data/spec/integration/type_test.rb +31 -8
  339. data/spec/model/association_reflection_spec.rb +207 -8
  340. data/spec/model/associations_spec.rb +1206 -693
  341. data/spec/model/base_spec.rb +334 -100
  342. data/spec/model/dataset_methods_spec.rb +45 -27
  343. data/spec/model/eager_loading_spec.rb +828 -692
  344. data/spec/model/hooks_spec.rb +355 -67
  345. data/spec/model/model_spec.rb +251 -179
  346. data/spec/model/plugins_spec.rb +24 -13
  347. data/spec/model/record_spec.rb +506 -232
  348. data/spec/model/spec_helper.rb +16 -77
  349. data/spec/model/validations_spec.rb +38 -2
  350. data/spec/rcov.opts +2 -0
  351. metadata +209 -80
  352. data/spec/core/core_sql_spec.rb +0 -451
@@ -1,20 +1,75 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The nested_attributes plugin allows you to update attributes for associated
4
- # objects directly through the parent object, similar to ActiveRecord's
5
- # Nested Attributes feature.
6
- #
7
- # Nested attributes are created using the nested_attributes method:
3
+ # The nested_attributes plugin allows you to create, update, and delete
4
+ # associated objects directly by calling a method on the current object.
5
+ # Nested attributes are defined using the nested_attributes class method:
8
6
  #
9
7
  # Artist.one_to_many :albums
10
8
  # Artist.plugin :nested_attributes
11
9
  # Artist.nested_attributes :albums
12
- # a = Artist.new(:name=>'YJM',
13
- # :albums_attributes=>[{:name=>'RF'}, {:name=>'MO'}])
14
- # # No database activity yet
15
10
  #
16
- # a.save # Saves artist and both albums
17
- # a.albums.map{|x| x.name} # ['RF', 'MO']
11
+ # The nested_attributes call defines a single method, <tt><i>association</i>_attributes=</tt>,
12
+ # (e.g. <tt>albums_attributes=</tt>). So if you have an Artist instance:
13
+ #
14
+ # a = Artist.new(:name=>'YJM')
15
+ #
16
+ # You can create new album instances related to this artist:
17
+ #
18
+ # a.albums_attributes = [{:name=>'RF'}, {:name=>'MO'}]
19
+ #
20
+ # Note that this doesn't send any queries to the database yet. That doesn't happen till
21
+ # you save the object:
22
+ #
23
+ # a.save
24
+ #
25
+ # That will save the artist first, and then save both albums. If either the artist
26
+ # is invalid or one of the albums is invalid, none of the objects will be saved to the
27
+ # database, and all related validation errors will be available in the artist's validation
28
+ # errors.
29
+ #
30
+ # In addition to creating new associated objects, you can also update existing associated
31
+ # objects. You just need to make sure that the primary key field is filled in for the
32
+ # associated object:
33
+ #
34
+ # a.update(:albums_attributes => [{:id=>1, :name=>'T'}])
35
+ #
36
+ # Since the primary key field is filled in, the plugin will update the album with id 1 instead
37
+ # of creating a new album.
38
+ #
39
+ # If you would like to delete the associated object instead of updating it, you add a _delete
40
+ # entry to the hash:
41
+ #
42
+ # a.update(:albums_attributes => [{:id=>1, :_delete=>true}])
43
+ #
44
+ # This will delete the related associated object from the database. If you want to leave the
45
+ # associated object in the database, but just remove it from the association, add a _remove
46
+ # entry in the hash:
47
+ #
48
+ # a.update(:albums_attributes => [{:id=>1, :_remove=>true}])
49
+ #
50
+ # The above example was for a one_to_many association, but the plugin also works similarly
51
+ # for other association types. For one_to_one and many_to_one associations, you need to
52
+ # pass a single hash instead of an array of hashes.
53
+ #
54
+ # This plugin is mainly designed to make it easy to use on html forms, where a single form
55
+ # submission can contained nested attributes (and even nested attributes of those attributes).
56
+ # You just need to name your form inputs correctly:
57
+ #
58
+ # artist[name]
59
+ # artist[albums_attributes][0][:name]
60
+ # artist[albums_attributes][1][:id]
61
+ # artist[albums_attributes][1][:name]
62
+ #
63
+ # Your web stack will probably parse that into a nested hash similar to:
64
+ #
65
+ # {:artist=>{:name=>?, :albums_attributes=>{0=>{:name=>?}, 1=>{:id=>?, :name=>?}}}}
66
+ #
67
+ # Then you can do:
68
+ #
69
+ # artist.update(params[:artist])
70
+ #
71
+ # To save changes to the artist, create the first album and associate it to the artist,
72
+ # and update the other existing associated album.
18
73
  module NestedAttributes
19
74
  # Depend on the instance_hooks plugin.
20
75
  def self.apply(model)
@@ -28,24 +83,37 @@ module Sequel
28
83
 
29
84
  # Allow nested attributes to be set for the given associations. Options:
30
85
  # * :destroy - Allow destruction of nested records.
31
- # * :fields - If provided, should be an Array. Restricts the fields allowed to be
32
- # modified through the association_attributes= method to the specific fields given.
86
+ # * :fields - If provided, should be an Array or proc. If it is an array,
87
+ # restricts the fields allowed to be modified through the
88
+ # association_attributes= method to the specific fields given. If it is
89
+ # a proc, it will be called with the associated object and should return an
90
+ # array of the allowable fields.
33
91
  # * :limit - For *_to_many associations, a limit on the number of records
34
92
  # that will be processed, to prevent denial of service attacks.
93
+ # * :reject_if - A proc that is given each attribute hash before it is
94
+ # passed to its associated object. If the proc returns a truthy
95
+ # value, the attribute hash is ignored.
35
96
  # * :remove - Allow disassociation of nested records (can remove the associated
36
97
  # object from the parent object, but not destroy the associated object).
37
- # * :strict - Set to false to not raise an error message if a primary key
38
- # is provided in a record, but it doesn't match an existing associated
39
- # object.
98
+ # * :strict - Kept for backward compatibility. Setting it to false is
99
+ # equivalent to setting :unmatched_pk to :ignore.
100
+ # * :transform - A proc to transform attribute hashes before they are
101
+ # passed to associated object. Takes two arguments, the parent object and
102
+ # the attribute hash. Uses the return value as the new attribute hash.
103
+ # * :unmatched_pk - Specify the action to be taken if a primary key is
104
+ # provided in a record, but it doesn't match an existing associated
105
+ # object. Set to :create to create a new object with that primary
106
+ # key, :ignore to ignore the record, or :raise to raise an error.
107
+ # The default is :raise.
40
108
  #
41
- # If a block is provided, it is passed each nested attribute hash. If
42
- # the hash should be ignored, the block should return anything except false or nil.
109
+ # If a block is provided, it is used to set the :reject_if option.
43
110
  def nested_attributes(*associations, &block)
44
111
  include(self.nested_attributes_module ||= Module.new) unless nested_attributes_module
45
112
  opts = associations.last.is_a?(Hash) ? associations.pop : {}
46
113
  reflections = associations.map{|a| association_reflection(a) || raise(Error, "no association named #{a} for #{self}")}
47
114
  reflections.each do |r|
48
115
  r[:nested_attributes] = opts
116
+ r[:nested_attributes][:unmatched_pk] ||= opts.delete(:strict) == false ? :ignore : :raise
49
117
  r[:nested_attributes][:reject_if] ||= block
50
118
  def_nested_attribute_method(r)
51
119
  end
@@ -78,7 +146,9 @@ module Sequel
78
146
  def nested_attributes_check_key_modifications(reflection, obj)
79
147
  keys = reflection.associated_object_keys.map{|x| obj.send(x)}
80
148
  yield
81
- raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed") unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
149
+ unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
150
+ raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed")
151
+ end
82
152
  end
83
153
 
84
154
  # Create a new associated object with the given attributes, validate
@@ -93,19 +163,19 @@ module Sequel
93
163
  after_save_hook{send(reflection.add_method, obj)}
94
164
  else
95
165
  associations[reflection[:name]] = obj
166
+
167
+ # Because we are modifying the associations cache manually before the
168
+ # setter is called, we still want to run the setter code even though
169
+ # the cached value will be the same as the given value.
170
+ @set_associated_object_if_same = true
171
+
96
172
  # Don't need to validate the object twice if :validate association option is not false
97
173
  # and don't want to validate it at all if it is false.
98
- send(reflection[:type] == :many_to_one ? :before_save_hook : :after_save_hook){send(reflection.setter_method, obj.save(:validate=>false))}
99
- end
100
- obj
101
- end
102
-
103
- # Find an associated object with the matching pk. If a matching option
104
- # is not found and the :strict option is not false, raise an Error.
105
- def nested_attributes_find(reflection, pk)
106
- pk = pk.to_s
107
- unless obj = Array(associated_objects = send(reflection[:name])).find{|x| x.pk.to_s == pk}
108
- raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})") unless reflection[:nested_attributes][:strict] == false
174
+ if reflection[:type] == :many_to_one
175
+ before_save_hook{send(reflection.setter_method, obj.save(:validate=>false))}
176
+ else
177
+ after_save_hook{send(reflection.setter_method, obj)}
178
+ end
109
179
  end
110
180
  obj
111
181
  end
@@ -122,72 +192,86 @@ module Sequel
122
192
  attributes_list.each{|a| nested_attributes_setter(reflection, a)}
123
193
  end
124
194
 
125
- # Remove the matching associated object from the current object.
126
- # If the :destroy option is given, destroy the object after disassociating it
195
+ # Remove the given associated object from the current object. If the
196
+ # :destroy option is given, destroy the object after disassociating it
127
197
  # (unless destroying the object would automatically disassociate it).
128
- # Returns the object removed, if it exists.
129
- def nested_attributes_remove(reflection, pk, opts={})
130
- if obj = nested_attributes_find(reflection, pk)
131
- if !opts[:destroy] || reflection.remove_before_destroy?
132
- before_save_hook do
133
- if reflection.returns_array?
134
- send(reflection.remove_method, obj)
135
- else
136
- send(reflection.setter_method, nil)
137
- end
198
+ # Returns the object removed.
199
+ def nested_attributes_remove(reflection, obj, opts={})
200
+ if !opts[:destroy] || reflection.remove_before_destroy?
201
+ before_save_hook do
202
+ if reflection.returns_array?
203
+ send(reflection.remove_method, obj)
204
+ else
205
+ send(reflection.setter_method, nil)
138
206
  end
139
207
  end
140
- after_save_hook{obj.destroy} if opts[:destroy]
141
- obj
142
208
  end
209
+ after_save_hook{obj.destroy} if opts[:destroy]
210
+ obj
143
211
  end
144
212
 
145
213
  # Set the fields in the obj based on the association, only allowing
146
214
  # specific :fields if configured.
147
215
  def nested_attributes_set_attributes(reflection, obj, attributes)
148
216
  if fields = reflection[:nested_attributes][:fields]
217
+ fields = fields.call(obj) if fields.respond_to?(:call)
149
218
  obj.set_only(attributes, fields)
150
219
  else
151
220
  obj.set(attributes)
152
221
  end
153
222
  end
154
223
 
155
- # Modify the associated object based on the contents of the attribtues hash:
224
+ # Modify the associated object based on the contents of the attributes hash:
225
+ # * If a :transform block was given to nested_attributes, use it to modify the attribute hash.
156
226
  # * If a block was given to nested_attributes, call it with the attributes and return immediately if the block returns true.
227
+ # * If a primary key exists in the attributes hash and it matches an associated object:
228
+ # ** If _delete is a key in the hash and the :destroy option is used, destroy the matching associated object.
229
+ # ** If _remove is a key in the hash and the :remove option is used, disassociated the matching associated object.
230
+ # ** Otherwise, update the matching associated object with the contents of the hash.
231
+ # * If a primary key exists in the attributes hash but it does not match an associated object,
232
+ # either raise an error, create a new object or ignore the hash, depending on the :unmatched_pk option.
157
233
  # * If no primary key exists in the attributes hash, create a new object.
158
- # * If _delete is a key in the hash and the :destroy option is used, destroy the matching associated object.
159
- # * If _remove is a key in the hash and the :remove option is used, disassociated the matching associated object.
160
- # * Otherwise, update the matching associated object with the contents of the hash.
161
234
  def nested_attributes_setter(reflection, attributes)
235
+ if a = reflection[:nested_attributes][:transform]
236
+ attributes = a.call(self, attributes)
237
+ end
162
238
  return if (b = reflection[:nested_attributes][:reject_if]) && b.call(attributes)
163
239
  modified!
164
240
  klass = reflection.associated_class
165
- if pk = attributes.delete(klass.primary_key) || attributes.delete(klass.primary_key.to_s)
166
- if klass.db.send(:typecast_value_boolean, attributes[:_delete] || attributes['_delete']) && reflection[:nested_attributes][:destroy]
167
- nested_attributes_remove(reflection, pk, :destroy=>true)
168
- elsif klass.db.send(:typecast_value_boolean, attributes[:_remove] || attributes['_remove']) && reflection[:nested_attributes][:remove]
169
- nested_attributes_remove(reflection, pk)
241
+ sym_keys = Array(klass.primary_key)
242
+ str_keys = sym_keys.map{|k| k.to_s}
243
+ if (pk = attributes.values_at(*sym_keys)).all? || (pk = attributes.values_at(*str_keys)).all?
244
+ pk = pk.map{|k| k.to_s}
245
+ obj = Array(send(reflection[:name])).find{|x| Array(x.pk).map{|k| k.to_s} == pk}
246
+ end
247
+ if obj
248
+ attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
249
+ if reflection[:nested_attributes][:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
250
+ nested_attributes_remove(reflection, obj, :destroy=>true)
251
+ elsif reflection[:nested_attributes][:remove] && klass.db.send(:typecast_value_boolean, attributes.delete(:_remove) || attributes.delete('_remove'))
252
+ nested_attributes_remove(reflection, obj)
170
253
  else
171
- nested_attributes_update(reflection, pk, attributes)
254
+ nested_attributes_update(reflection, obj, attributes)
255
+ end
256
+ elsif pk.all? && reflection[:nested_attributes][:unmatched_pk] != :create
257
+ if reflection[:nested_attributes][:unmatched_pk] == :raise
258
+ raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})")
172
259
  end
173
260
  else
174
261
  nested_attributes_create(reflection, attributes)
175
262
  end
176
263
  end
177
264
 
178
- # Update the matching associated object with the attributes,
179
- # validating it when the parent object is validated and saving it
180
- # when the parent is saved.
181
- # Returns the object updated, if it exists.
182
- def nested_attributes_update(reflection, pk, attributes)
183
- if obj = nested_attributes_find(reflection, pk)
184
- nested_attributes_update_attributes(reflection, obj, attributes)
185
- after_validation_hook{validate_associated_object(reflection, obj)}
186
- # Don't need to validate the object twice if :validate association option is not false
187
- # and don't want to validate it at all if it is false.
188
- after_save_hook{obj.save_changes(:validate=>false)}
189
- obj
190
- end
265
+ # Update the given object with the attributes, validating it when the
266
+ # parent object is validated and saving it when the parent is saved.
267
+ # Returns the object updated.
268
+ def nested_attributes_update(reflection, obj, attributes)
269
+ nested_attributes_update_attributes(reflection, obj, attributes)
270
+ after_validation_hook{validate_associated_object(reflection, obj)}
271
+ # Don't need to validate the object twice if :validate association option is not false
272
+ # and don't want to validate it at all if it is false.
273
+ after_save_hook{obj.save_changes(:validate=>false)}
274
+ obj
191
275
  end
192
276
 
193
277
  # Update the attributes for the given object related to the current object through the association.
@@ -65,6 +65,14 @@ module Sequel
65
65
  lc = model.lock_column
66
66
  instance_filter(lc=>send(lc))
67
67
  end
68
+
69
+ # Clear the instance filters when refreshing, so that attempting to
70
+ # refresh after a failed save removes the previous lock column filter
71
+ # (the new one will be added before updating).
72
+ def _refresh(ds)
73
+ clear_instance_filters
74
+ super
75
+ end
68
76
 
69
77
  # Only update the row if it has the same lock version, and increment the
70
78
  # lock version.
@@ -0,0 +1,121 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The pg_row plugin allows you to use Sequel::Model classes as composite type
4
+ # classes, via the pg_row extension. So if you have an address table:
5
+ #
6
+ # DB.create_table(:address) do
7
+ # String :street
8
+ # String :city
9
+ # String :zip
10
+ # end
11
+ #
12
+ # and a company table with an address:
13
+ #
14
+ # DB.create_table(:company) do
15
+ # String :name
16
+ # address :address
17
+ # end
18
+ #
19
+ # You can create a Sequel::Model for the address table, and load the plugin,
20
+ # which registers the row type:
21
+ #
22
+ # class Address < Sequel::Model(:address)
23
+ # plugin :pg_row
24
+ # end
25
+ #
26
+ # Then when you select from the company table (even using a plain dataset),
27
+ # it will return address values as instances of Address:
28
+ #
29
+ # DB[:company].first
30
+ # # => {:name=>'MS', :address=>
31
+ # Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345')}
32
+ #
33
+ # If you want a lot of your models to be used as row types, you can load the
34
+ # plugin into Sequel::Model itself:
35
+ #
36
+ # Sequel::Model.plugin :pg_row
37
+ #
38
+ # And then call register_row_type in the class
39
+ #
40
+ # Address.register_row_type
41
+ #
42
+ # Note that automatic conversion only works with the native postgres adapter.
43
+ # For other adapters that connect to PostgreSQL, you need to call the conversion
44
+ # proc manually.
45
+ #
46
+ # In addition to returning row-valued/composite types as instances of Sequel::Model,
47
+ # this also lets you use model instances in datasets when inserting, updating, and
48
+ # filtering:
49
+ #
50
+ # DB[:company].insert(:name=>'MS', :address=>
51
+ # Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345'))
52
+ module PgRow
53
+ # When loading the extension, make sure the database has the pg_row extension
54
+ # loaded, load the custom database extensions, and automatically register the
55
+ # row type if the model has a dataset.
56
+ def self.configure(model)
57
+ model.db.extension(:pg_row)
58
+ model.db.extend(DatabaseMethods)
59
+ model.register_row_type if model.instance_variable_get(:@dataset)
60
+ end
61
+
62
+ module DatabaseMethods
63
+ ESCAPE_RE = /("|\\)/.freeze
64
+ ESCAPE_REPLACEMENT = '\\\\\1'.freeze
65
+ COMMA = ','
66
+
67
+ # Handle Sequel::Model instances in bound variables.
68
+ def bound_variable_arg(arg, conn)
69
+ case arg
70
+ when Sequel::Model
71
+ "(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA)})"
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ # If a Sequel::Model instance is given, return it as-is
78
+ # instead of attempting to convert it.
79
+ def row_type(db_type, v)
80
+ if v.is_a?(Sequel::Model)
81
+ v
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ # Handle Sequel::Model instances in bound variable arrays.
90
+ def bound_variable_array(arg)
91
+ case arg
92
+ when Sequel::Model
93
+ "\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
94
+ else
95
+ super
96
+ end
97
+ end
98
+ end
99
+
100
+ module ClassMethods
101
+ # Register the model's row type with the database.
102
+ def register_row_type
103
+ db.register_row_type(model.table_name, :converter=>self, :typecaster=>method(:new))
104
+ end
105
+ end
106
+
107
+ module InstanceMethods
108
+ ROW = 'ROW'.freeze
109
+ CAST = '::'.freeze
110
+
111
+ # Literalize the model instance and append it to the sql.
112
+ def sql_literal_append(ds, sql)
113
+ sql << ROW
114
+ ds.literal_append(sql, values.values_at(*columns))
115
+ sql << CAST
116
+ ds.quote_schema_table_append(sql, model.table_name)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,65 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The PgTypecastOnLoad plugin exists because when you connect to PostgreSQL
4
+ # using the do, swift, or jdbc adapter, Sequel doesn't have complete
5
+ # control over typecasting, and may return columns as strings instead of how
6
+ # the native postgres adapter would typecast them. This is mostly needed for
7
+ # the additional support that the pg_* extensions add for advanced PostgreSQL
8
+ # types such as arrays.
9
+ #
10
+ # This plugin modifies Model#set_values to do the same conversion that the
11
+ # native postgres adapter would do for all columns given. You can either
12
+ # specify the columns to typecast on load in the plugin call itself, or
13
+ # afterwards using add_pg_typecast_on_load_columns:
14
+ #
15
+ # # aliases => text[] column
16
+ # # config => hstore column
17
+ #
18
+ # Album.plugin :pg_typecast_on_load, :aliases, :config
19
+ # # or:
20
+ # Album.plugin :pg_typecast_on_load
21
+ # Album.add_pg_typecast_on_load_columns :aliases, :config
22
+ #
23
+ # This plugin only handles values that the adapter returns as strings. If
24
+ # the adapter returns a value other than a string for a column, that value
25
+ # will be used directly without typecasting.
26
+ module PgTypecastOnLoad
27
+ # Call add_pg_typecast_on_load_columns on the passed column arguments.
28
+ def self.configure(model, *columns)
29
+ model.instance_eval do
30
+ @pg_typecast_on_load_columns ||= []
31
+ add_pg_typecast_on_load_columns(*columns)
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ # The columns to typecast on load for this model.
37
+ attr_reader :pg_typecast_on_load_columns
38
+
39
+ # Add additional columns to typecast on load for this model.
40
+ def add_pg_typecast_on_load_columns(*columns)
41
+ @pg_typecast_on_load_columns.concat(columns)
42
+ end
43
+
44
+ # Give the subclass a copy of the columns to typecast on load.
45
+ def inherited(subclass)
46
+ super
47
+ subclass.instance_variable_set(:@pg_typecast_on_load_columns, pg_typecast_on_load_columns.dup)
48
+ end
49
+ end
50
+
51
+ module InstanceMethods
52
+ # Lookup the conversion proc for the column's oid in the Database
53
+ # object, and use it to convert the value.
54
+ def set_values(values)
55
+ model.pg_typecast_on_load_columns.each do |c|
56
+ if (v = values[c]).is_a?(String) && (oid = db_schema[c][:oid])
57
+ values[c] = db.conversion_procs[oid].call(v)
58
+ end
59
+ end
60
+ super
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,167 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The prepared_statements plugin modifies the model to use prepared statements for
4
+ # instance level deletes and saves, as well as class level lookups by
5
+ # primary key.
6
+ #
7
+ # Note that this plugin is unsafe in some circumstances, as it can allow up to
8
+ # 2^N prepared statements to be created for each type of insert and update query, where
9
+ # N is the number of colums in the table. It is recommended that you use the
10
+ # +prepared_statements_safe+ plugin in addition to this plugin to reduce the number
11
+ # of prepared statements that can be created, unless you tightly control how your
12
+ # model instances are saved.
13
+ #
14
+ # This plugin probably does not work correctly with the instance filters plugin.
15
+ #
16
+ # Usage:
17
+ #
18
+ # # Make all model subclasses use prepared statements (called before loading subclasses)
19
+ # Sequel::Model.plugin :prepared_statements
20
+ #
21
+ # # Make the Album class use prepared statements
22
+ # Album.plugin :prepared_statements
23
+ module PreparedStatements
24
+ # Synchronize access to the integer sequence so that no two calls get the same integer.
25
+ MUTEX = Mutex.new
26
+
27
+ i = 0
28
+ # This plugin names prepared statements uniquely using an integer sequence, this
29
+ # lambda returns the next integer to use.
30
+ NEXT = lambda{MUTEX.synchronize{i += 1}}
31
+
32
+ # Setup the datastructure used to hold the prepared statements in the model.
33
+ def self.apply(model)
34
+ model.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
35
+ end
36
+
37
+ module ClassMethods
38
+ # Setup the datastructure used to hold the prepared statements in the subclass.
39
+ def inherited(subclass)
40
+ super
41
+ subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
42
+ end
43
+
44
+ private
45
+
46
+ # Create a prepared statement based on the given dataset with a unique name for the given
47
+ # type of query and values.
48
+ def prepare_statement(ds, type, vals={})
49
+ ps = ds.prepare(type, :"smpsp_#{NEXT.call}", vals)
50
+ ps.log_sql = true
51
+ ps
52
+ end
53
+
54
+ # Return a sorted array of columns for use as a hash key.
55
+ def prepared_columns(cols)
56
+ RUBY_VERSION >= '1.9' ? cols.sort : cols.sort_by{|c| c.to_s}
57
+ end
58
+
59
+ # Return a prepared statement that can be used to delete a row from this model's dataset.
60
+ def prepared_delete
61
+ cached_prepared_statement(:fixed, :delete){prepare_statement(filter(prepared_statement_key_array(primary_key)), :delete)}
62
+ end
63
+
64
+ # Return a prepared statement that can be used to insert a row using the given columns.
65
+ def prepared_insert(cols)
66
+ cached_prepared_statement(:insert, prepared_columns(cols)){prepare_statement(dataset, :insert, prepared_statement_key_hash(cols))}
67
+ end
68
+
69
+ # Return a prepared statement that can be used to insert a row using the given columns
70
+ # and return that column values for the row created.
71
+ def prepared_insert_select(cols)
72
+ if dataset.supports_insert_select?
73
+ cached_prepared_statement(:insert_select, prepared_columns(cols)){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(cols))}
74
+ end
75
+ end
76
+
77
+ # Return a prepared statement that can be used to lookup a row solely based on the primary key.
78
+ def prepared_lookup
79
+ cached_prepared_statement(:fixed, :lookup){prepare_statement(filter(prepared_statement_key_array(primary_key)), :first)}
80
+ end
81
+
82
+ # Return a prepared statement that can be used to refresh a row to get new column values after insertion.
83
+ def prepared_refresh
84
+ cached_prepared_statement(:fixed, :refresh){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)}
85
+ end
86
+
87
+ # Return an array of two element arrays with the column symbol as the first entry and the
88
+ # placeholder symbol as the second entry.
89
+ def prepared_statement_key_array(keys)
90
+ if dataset.requires_placeholder_type_specifiers?
91
+ sch = db_schema
92
+ Array(keys).map do |k|
93
+ if (s = sch[k]) && (t = s[:type])
94
+ [k, :"$#{k}__#{t}"]
95
+ else
96
+ [k, :"$#{k}"]
97
+ end
98
+ end
99
+ else
100
+ Array(keys).map{|k| [k, :"$#{k}"]}
101
+ end
102
+ end
103
+
104
+ # Return a hash mapping column symbols to placeholder symbols.
105
+ def prepared_statement_key_hash(keys)
106
+ Hash[*(prepared_statement_key_array(keys).flatten)]
107
+ end
108
+
109
+ # Return a prepared statement that can be used to update row using the given columns.
110
+ def prepared_update(cols)
111
+ cached_prepared_statement(:update, prepared_columns(cols)){prepare_statement(filter(prepared_statement_key_array(primary_key)), :update, prepared_statement_key_hash(cols))}
112
+ end
113
+
114
+ # Use a prepared statement to query the database for the row matching the given primary key.
115
+ def primary_key_lookup(pk)
116
+ prepared_lookup.call(primary_key_hash(pk))
117
+ end
118
+
119
+ private
120
+
121
+ # If a prepared statement has already been cached for the given type and subtype,
122
+ # return it. Otherwise, yield to the block to get the prepared statement, and cache it.
123
+ def cached_prepared_statement(type, subtype)
124
+ h = @prepared_statements[type]
125
+ Sequel.synchronize do
126
+ if v = h[subtype]
127
+ return v end
128
+ end
129
+ ps = yield
130
+ Sequel.synchronize{h[subtype] = ps}
131
+ end
132
+ end
133
+
134
+ module InstanceMethods
135
+ private
136
+
137
+ # Use a prepared statement to delete the row.
138
+ def _delete_without_checking
139
+ model.send(:prepared_delete).call(pk_hash)
140
+ end
141
+
142
+ # Use a prepared statement to insert the values into the model's dataset.
143
+ def _insert_raw(ds)
144
+ model.send(:prepared_insert, @values.keys).call(@values)
145
+ end
146
+
147
+ # Use a prepared statement to insert the values into the model's dataset
148
+ # and return the new column values.
149
+ def _insert_select_raw(ds)
150
+ if ps = model.send(:prepared_insert_select, @values.keys)
151
+ ps.call(@values)
152
+ end
153
+ end
154
+
155
+ # Use a prepared statement to refresh this model's column values.
156
+ def _refresh_get(ds)
157
+ model.send(:prepared_refresh).call(pk_hash)
158
+ end
159
+
160
+ # Use a prepared statement to update this model's columns in the database.
161
+ def _update_without_checking(columns)
162
+ model.send(:prepared_update, columns.keys).call(columns.merge(pk_hash))
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end