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
@@ -13,147 +13,296 @@ logger = Object.new
13
13
  def logger.method_missing(m, msg)
14
14
  POSTGRES_DB.sqls << msg
15
15
  end
16
- POSTGRES_DB.loggers = [logger]
16
+ POSTGRES_DB.loggers << logger
17
17
 
18
- #POSTGRES_DB.instance_variable_set(:@server_version, 80100)
19
- POSTGRES_DB.create_table! :test do
20
- text :name
21
- integer :value, :index => true
22
- end
23
- POSTGRES_DB.create_table! :test2 do
24
- text :name
25
- integer :value
26
- end
27
- POSTGRES_DB.create_table! :test3 do
28
- integer :value
29
- timestamp :time
30
- end
31
- POSTGRES_DB.create_table! :test4 do
32
- varchar :name, :size => 20
33
- bytea :value
18
+ #POSTGRES_DB.instance_variable_set(:@server_version, 80200)
19
+
20
+ describe "PostgreSQL", '#create_table' do
21
+ before do
22
+ @db = POSTGRES_DB
23
+ POSTGRES_DB.sqls.clear
24
+ end
25
+ after do
26
+ @db.drop_table?(:tmp_dolls)
27
+ @db.drop_table?(:unlogged_dolls)
28
+ end
29
+
30
+ specify "should create a temporary table" do
31
+ @db.create_table(:tmp_dolls, :temp => true){text :name}
32
+ check_sqls do
33
+ @db.sqls.should == ['CREATE TEMPORARY TABLE "tmp_dolls" ("name" text)']
34
+ end
35
+ end
36
+
37
+ specify "should create an unlogged table" do
38
+ @db.create_table(:unlogged_dolls, :unlogged => true){text :name}
39
+ check_sqls do
40
+ @db.sqls.should == ['CREATE UNLOGGED TABLE "unlogged_dolls" ("name" text)']
41
+ end
42
+ end
43
+
44
+ specify "should not allow to pass both :temp and :unlogged" do
45
+ proc do
46
+ @db.create_table(:temp_unlogged_dolls, :temp => true, :unlogged => true){text :name}
47
+ end.should raise_error(Sequel::Error, "can't provide both :temp and :unlogged to create_table")
48
+ end
34
49
  end
35
50
 
36
51
  describe "A PostgreSQL database" do
37
- before do
52
+ before(:all) do
38
53
  @db = POSTGRES_DB
54
+ @db.create_table!(:public__testfk){primary_key :id; foreign_key :i, :public__testfk}
55
+ end
56
+ after(:all) do
57
+ @db.drop_table?(:public__testfk)
39
58
  end
40
-
59
+
41
60
  specify "should provide the server version" do
42
61
  @db.server_version.should > 70000
43
62
  end
44
63
 
45
64
  specify "should correctly parse the schema" do
46
- @db.schema(:test3, :reload=>true).should == [
47
- [:value, {:type=>:integer, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"integer", :primary_key=>false}],
48
- [:time, {:type=>:datetime, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
49
- ]
50
- @db.schema(:test4, :reload=>true).should == [
51
- [:name, {:type=>:string, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
52
- [:value, {:type=>:blob, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"bytea", :primary_key=>false}]
53
- ]
65
+ @db.schema(:public__testfk, :reload=>true).should == [
66
+ [:id, {:type=>:integer, :ruby_default=>nil, :db_type=>"integer", :default=>"nextval('testfk_id_seq'::regclass)", :oid=>23, :primary_key=>true, :allow_null=>false}],
67
+ [:i, {:type=>:integer, :ruby_default=>nil, :db_type=>"integer", :default=>nil, :oid=>23, :primary_key=>false, :allow_null=>true}]]
68
+ end
69
+
70
+ specify "should parse foreign keys for tables in a schema" do
71
+ @db.foreign_key_list(:public__testfk).should == [{:on_delete=>:no_action, :on_update=>:no_action, :columns=>[:i], :key=>[:id], :deferrable=>false, :table=>Sequel.qualify(:public, :testfk), :name=>:testfk_i_fkey}]
72
+ end
73
+
74
+ specify "should return uuid fields as strings" do
75
+ @db.get(Sequel.cast('550e8400-e29b-41d4-a716-446655440000', :uuid)).should == '550e8400-e29b-41d4-a716-446655440000'
54
76
  end
55
77
  end
56
78
 
57
79
  describe "A PostgreSQL dataset" do
80
+ before(:all) do
81
+ @db = POSTGRES_DB
82
+ @d = @db[:test]
83
+ @db.create_table! :test do
84
+ text :name
85
+ integer :value, :index => true
86
+ end
87
+ end
58
88
  before do
59
- @d = POSTGRES_DB[:test]
60
- @d.delete # remove all records
89
+ @d.delete
90
+ @db.sqls.clear
61
91
  end
62
-
92
+ after do
93
+ @db.drop_table?(:atest)
94
+ end
95
+ after(:all) do
96
+ @db.drop_table?(:test)
97
+ end
98
+
63
99
  specify "should quote columns and tables using double quotes if quoting identifiers" do
64
- @d.quote_identifiers = true
65
- @d.select(:name).sql.should == \
66
- 'SELECT "name" FROM "test"'
67
-
68
- @d.select('COUNT(*)'.lit).sql.should == \
69
- 'SELECT COUNT(*) FROM "test"'
100
+ check_sqls do
101
+ @d.select(:name).sql.should == 'SELECT "name" FROM "test"'
102
+ @d.select(Sequel.lit('COUNT(*)')).sql.should == 'SELECT COUNT(*) FROM "test"'
103
+ @d.select(Sequel.function(:max, :value)).sql.should == 'SELECT max("value") FROM "test"'
104
+ @d.select(Sequel.function(:NOW)).sql.should == 'SELECT NOW() FROM "test"'
105
+ @d.select(Sequel.function(:max, :items__value)).sql.should == 'SELECT max("items"."value") FROM "test"'
106
+ @d.order(Sequel.desc(:name)).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC'
107
+ @d.select(Sequel.lit('test.name AS item_name')).sql.should == 'SELECT test.name AS item_name FROM "test"'
108
+ @d.select(Sequel.lit('"name"')).sql.should == 'SELECT "name" FROM "test"'
109
+ @d.select(Sequel.lit('max(test."name") AS "max_name"')).sql.should == 'SELECT max(test."name") AS "max_name" FROM "test"'
110
+ @d.insert_sql(:x => :y).should =~ /\AINSERT INTO "test" \("x"\) VALUES \("y"\)( RETURNING NULL)?\z/
111
+
112
+ @d.select(Sequel.function(:test, :abc, 'hello')).sql.should == "SELECT test(\"abc\", 'hello') FROM \"test\""
113
+ @d.select(Sequel.function(:test, :abc__def, 'hello')).sql.should == "SELECT test(\"abc\".\"def\", 'hello') FROM \"test\""
114
+ @d.select(Sequel.function(:test, :abc__def, 'hello').as(:x2)).sql.should == "SELECT test(\"abc\".\"def\", 'hello') AS \"x2\" FROM \"test\""
115
+ @d.insert_sql(:value => 333).should =~ /\AINSERT INTO "test" \("value"\) VALUES \(333\)( RETURNING NULL)?\z/
116
+ end
117
+ end
118
+
119
+ specify "should quote fields correctly when reversing the order if quoting identifiers" do
120
+ check_sqls do
121
+ @d.reverse_order(:name).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC'
122
+ @d.reverse_order(Sequel.desc(:name)).sql.should == 'SELECT * FROM "test" ORDER BY "name" ASC'
123
+ @d.reverse_order(:name, Sequel.desc(:test)).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC, "test" ASC'
124
+ @d.reverse_order(Sequel.desc(:name), :test).sql.should == 'SELECT * FROM "test" ORDER BY "name" ASC, "test" DESC'
125
+ end
126
+ end
70
127
 
71
- @d.select(:max.sql_function(:value)).sql.should == \
72
- 'SELECT max("value") FROM "test"'
73
-
74
- @d.select(:NOW.sql_function).sql.should == \
75
- 'SELECT NOW() FROM "test"'
128
+ specify "should support regexps" do
129
+ @d << {:name => 'abc', :value => 1}
130
+ @d << {:name => 'bcd', :value => 2}
131
+ @d.filter(:name => /bc/).count.should == 2
132
+ @d.filter(:name => /^bc/).count.should == 1
133
+ end
76
134
 
77
- @d.select(:max.sql_function(:items__value)).sql.should == \
78
- 'SELECT max("items"."value") FROM "test"'
135
+ specify "should support NULLS FIRST and NULLS LAST" do
136
+ @d << {:name => 'abc'}
137
+ @d << {:name => 'bcd'}
138
+ @d << {:name => 'bcd', :value => 2}
139
+ @d.order(Sequel.asc(:value, :nulls=>:first), :name).select_map(:name).should == %w[abc bcd bcd]
140
+ @d.order(Sequel.asc(:value, :nulls=>:last), :name).select_map(:name).should == %w[bcd abc bcd]
141
+ @d.order(Sequel.asc(:value, :nulls=>:first), :name).reverse.select_map(:name).should == %w[bcd bcd abc]
142
+ end
79
143
 
80
- @d.order(:name.desc).sql.should == \
81
- 'SELECT * FROM "test" ORDER BY "name" DESC'
144
+ specify "#lock should lock tables and yield if a block is given" do
145
+ @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
146
+ end
82
147
 
83
- @d.select('test.name AS item_name'.lit).sql.should == \
84
- 'SELECT test.name AS item_name FROM "test"'
85
-
86
- @d.select('"name"'.lit).sql.should == \
87
- 'SELECT "name" FROM "test"'
148
+ specify "should support exclusion constraints when creating or altering tables" do
149
+ @db.create_table!(:atest){Integer :t; exclude [[Sequel.desc(:t, :nulls=>:last), '=']], :using=>:btree, :where=>proc{t > 0}}
150
+ @db[:atest].insert(1)
151
+ @db[:atest].insert(2)
152
+ proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
88
153
 
89
- @d.select('max(test."name") AS "max_name"'.lit).sql.should == \
90
- 'SELECT max(test."name") AS "max_name" FROM "test"'
91
-
92
- @d.select(:test.sql_function(:abc, 'hello')).sql.should == \
93
- "SELECT test(\"abc\", 'hello') FROM \"test\""
154
+ @db.create_table!(:atest){Integer :t}
155
+ @db.alter_table(:atest){add_exclusion_constraint [[:t, '=']], :using=>:btree, :name=>'atest_ex'}
156
+ @db[:atest].insert(1)
157
+ @db[:atest].insert(2)
158
+ proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
159
+ @db.alter_table(:atest){drop_constraint 'atest_ex'}
160
+ end if POSTGRES_DB.server_version >= 90000
94
161
 
95
- @d.select(:test.sql_function(:abc__def, 'hello')).sql.should == \
96
- "SELECT test(\"abc\".\"def\", 'hello') FROM \"test\""
162
+ specify "should support adding foreign key constarints that are not yet valid, and validating them later" do
163
+ @db.create_table!(:atest){primary_key :id; Integer :fk}
164
+ @db[:atest].insert(1, 5)
165
+ @db.alter_table(:atest){add_foreign_key [:fk], :atest, :not_valid=>true, :name=>:atest_fk}
166
+ @db[:atest].insert(2, 1)
167
+ proc{@db[:atest].insert(3, 4)}.should raise_error(Sequel::DatabaseError)
97
168
 
98
- @d.select(:test.sql_function(:abc__def, 'hello').as(:x2)).sql.should == \
99
- "SELECT test(\"abc\".\"def\", 'hello') AS \"x2\" FROM \"test\""
169
+ proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should raise_error(Sequel::DatabaseError)
170
+ @db[:atest].where(:id=>1).update(:fk=>2)
171
+ @db.alter_table(:atest){validate_constraint :atest_fk}
172
+ proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should_not raise_error
173
+ end if POSTGRES_DB.server_version >= 90200
100
174
 
101
- @d.insert_sql(:value => 333).should =~ \
102
- /\AINSERT INTO "test" \("value"\) VALUES \(333\)( RETURNING NULL)?\z/
175
+ specify "should support :using when altering a column's type" do
176
+ @db.create_table!(:atest){Integer :t}
177
+ @db[:atest].insert(1262304000)
178
+ @db.alter_table(:atest){set_column_type :t, Time, :using=>Sequel.cast('epoch', Time) + Sequel.cast('1 second', :interval) * :t}
179
+ @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
180
+ end
103
181
 
104
- @d.insert_sql(:x => :y).should =~ \
105
- /\AINSERT INTO "test" \("x"\) VALUES \("y"\)( RETURNING NULL)?\z/
182
+ specify "should support :using with a string when altering a column's type" do
183
+ @db.create_table!(:atest){Integer :t}
184
+ @db[:atest].insert(1262304000)
185
+ @db.alter_table(:atest){set_column_type :t, Time, :using=>"'epoch'::timestamp + '1 second'::interval * t"}
186
+ @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
187
+ end
106
188
 
107
- @d.disable_insert_returning.insert_sql(:value => 333).should =~ \
108
- /\AINSERT INTO "test" \("value"\) VALUES \(333\)\z/
189
+ specify "should be able to parse the default value for an interval type" do
190
+ @db.create_table!(:atest){interval :t, :default=>'1 week'}
191
+ @db.schema(:atest).first.last[:ruby_default].should == '7 days'
109
192
  end
110
-
111
- specify "should quote fields correctly when reversing the order if quoting identifiers" do
112
- @d.quote_identifiers = true
113
- @d.reverse_order(:name).sql.should == \
114
- 'SELECT * FROM "test" ORDER BY "name" DESC'
115
193
 
116
- @d.reverse_order(:name.desc).sql.should == \
117
- 'SELECT * FROM "test" ORDER BY "name" ASC'
194
+ specify "should have #transaction support various types of synchronous options" do
195
+ @db.transaction(:synchronous=>:on){}
196
+ @db.transaction(:synchronous=>true){}
197
+ @db.transaction(:synchronous=>:off){}
198
+ @db.transaction(:synchronous=>false){}
199
+ @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = on", "SET LOCAL synchronous_commit = on", "SET LOCAL synchronous_commit = off", "SET LOCAL synchronous_commit = off"]
118
200
 
119
- @d.reverse_order(:name, :test.desc).sql.should == \
120
- 'SELECT * FROM "test" ORDER BY "name" DESC, "test" ASC'
201
+ @db.sqls.clear
202
+ @db.transaction(:synchronous=>nil){}
203
+ check_sqls do
204
+ @db.sqls.should == ['BEGIN', 'COMMIT']
205
+ end
206
+
207
+ if @db.server_version >= 90100
208
+ @db.sqls.clear
209
+ @db.transaction(:synchronous=>:local){}
210
+ check_sqls do
211
+ @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = local"]
212
+ end
121
213
 
122
- @d.reverse_order(:name.desc, :test).sql.should == \
123
- 'SELECT * FROM "test" ORDER BY "name" ASC, "test" DESC'
214
+ if @db.server_version >= 90200
215
+ @db.sqls.clear
216
+ @db.transaction(:synchronous=>:remote_write){}
217
+ check_sqls do
218
+ @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = remote_write"]
219
+ end
220
+ end
221
+ end
124
222
  end
125
223
 
126
- specify "should support regexps" do
127
- @d << {:name => 'abc', :value => 1}
128
- @d << {:name => 'bcd', :value => 2}
129
- @d.filter(:name => /bc/).count.should == 2
130
- @d.filter(:name => /^bc/).count.should == 1
224
+ specify "should have #transaction support read only transactions" do
225
+ @db.transaction(:read_only=>true){}
226
+ @db.transaction(:read_only=>false){}
227
+ @db.transaction(:isolation=>:serializable, :read_only=>true){}
228
+ @db.transaction(:isolation=>:serializable, :read_only=>false){}
229
+ @db.sqls.grep(/READ/).should == ["SET TRANSACTION READ ONLY", "SET TRANSACTION READ WRITE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE"]
230
+ end
231
+
232
+ specify "should have #transaction support deferrable transactions" do
233
+ @db.transaction(:deferrable=>true){}
234
+ @db.transaction(:deferrable=>false){}
235
+ @db.transaction(:deferrable=>true, :read_only=>true){}
236
+ @db.transaction(:deferrable=>false, :read_only=>false){}
237
+ @db.transaction(:isolation=>:serializable, :deferrable=>true, :read_only=>true){}
238
+ @db.transaction(:isolation=>:serializable, :deferrable=>false, :read_only=>false){}
239
+ @db.sqls.grep(/DEF/).should == ["SET TRANSACTION DEFERRABLE", "SET TRANSACTION NOT DEFERRABLE", "SET TRANSACTION READ ONLY DEFERRABLE", "SET TRANSACTION READ WRITE NOT DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE NOT DEFERRABLE"]
240
+ end if POSTGRES_DB.server_version >= 90100
241
+
242
+ specify "should support creating indexes concurrently" do
243
+ @db.add_index :test, [:name, :value], :concurrently=>true
244
+ check_sqls do
245
+ @db.sqls.should == ['CREATE INDEX CONCURRENTLY "test_name_value_index" ON "test" ("name", "value")']
246
+ end
131
247
  end
132
-
133
- specify "should support NULLS FIRST and NULLS LAST" do
134
- @d << {:name => 'abc'}
135
- @d << {:name => 'bcd'}
136
- @d << {:name => 'bcd', :value => 2}
137
- @d.order(:value.asc(:nulls=>:first), :name).select_map(:name).should == %w[abc bcd bcd]
138
- @d.order(:value.asc(:nulls=>:last), :name).select_map(:name).should == %w[bcd abc bcd]
139
- @d.order(:value.asc(:nulls=>:first), :name).reverse.select_map(:name).should == %w[bcd bcd abc]
248
+
249
+ specify "should support dropping indexes only if they already exist" do
250
+ @db.add_index :test, [:name, :value], :name=>'tnv1'
251
+ @db.sqls.clear
252
+ @db.drop_index :test, [:name, :value], :if_exists=>true, :name=>'tnv1'
253
+ check_sqls do
254
+ @db.sqls.should == ['DROP INDEX IF EXISTS "tnv1"']
255
+ end
140
256
  end
141
-
142
- specify "#lock should lock tables and yield if a block is given" do
143
- @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
257
+
258
+ specify "should support CASCADE when dropping indexes" do
259
+ @db.add_index :test, [:name, :value], :name=>'tnv2'
260
+ @db.sqls.clear
261
+ @db.drop_index :test, [:name, :value], :cascade=>true, :name=>'tnv2'
262
+ check_sqls do
263
+ @db.sqls.should == ['DROP INDEX "tnv2" CASCADE']
264
+ end
144
265
  end
145
-
266
+
267
+ specify "should support dropping indexes concurrently" do
268
+ @db.add_index :test, [:name, :value], :name=>'tnv2'
269
+ @db.sqls.clear
270
+ @db.drop_index :test, [:name, :value], :concurrently=>true, :name=>'tnv2'
271
+ check_sqls do
272
+ @db.sqls.should == ['DROP INDEX CONCURRENTLY "tnv2"']
273
+ end
274
+ end if POSTGRES_DB.server_version >= 90200
275
+
146
276
  specify "#lock should lock table if inside a transaction" do
147
- POSTGRES_DB.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
277
+ @db.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
148
278
  end
149
-
279
+
150
280
  specify "#lock should return nil" do
151
281
  @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}.should == nil
152
- POSTGRES_DB.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
282
+ @db.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
153
283
  end
154
-
284
+
155
285
  specify "should raise an error if attempting to update a joined dataset with a single FROM table" do
156
- proc{POSTGRES_DB[:test].join(:test2, [:name]).update(:name=>'a')}.should raise_error(Sequel::Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs')
286
+ proc{@db[:test].join(:test, [:name]).update(:name=>'a')}.should raise_error(Sequel::Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs')
287
+ end
288
+
289
+ specify "should truncate with options" do
290
+ @d << { :name => 'abc', :value => 1}
291
+ @d.count.should == 1
292
+ @d.truncate(:cascade => true)
293
+ @d.count.should == 0
294
+ if @d.db.server_version > 80400
295
+ @d << { :name => 'abc', :value => 1}
296
+ @d.truncate(:cascade => true, :only=>true, :restart=>true)
297
+ @d.count.should == 0
298
+ end
299
+ end
300
+
301
+ specify "should truncate multiple tables at once" do
302
+ tables = [:test, :test]
303
+ tables.each{|t| @d.from(t).insert}
304
+ @d.from(:test, :test).truncate
305
+ tables.each{|t| @d.from(t).count.should == 0}
157
306
  end
158
307
  end
159
308
 
@@ -167,16 +316,16 @@ describe "Dataset#distinct" do
167
316
  @ds = @db[:a]
168
317
  end
169
318
  after do
170
- @db.drop_table(:a)
319
+ @db.drop_table?(:a)
171
320
  end
172
-
321
+
173
322
  it "#distinct with arguments should return results distinct on those arguments" do
174
323
  @ds.insert(20, 10)
175
324
  @ds.insert(30, 10)
176
325
  @ds.order(:b, :a).distinct.map(:a).should == [20, 30]
177
- @ds.order(:b, :a.desc).distinct.map(:a).should == [30, 20]
326
+ @ds.order(:b, Sequel.desc(:a)).distinct.map(:a).should == [30, 20]
178
327
  @ds.order(:b, :a).distinct(:b).map(:a).should == [20]
179
- @ds.order(:b, :a.desc).distinct(:b).map(:a).should == [30]
328
+ @ds.order(:b, Sequel.desc(:a)).distinct(:b).map(:a).should == [30]
180
329
  end
181
330
  end
182
331
 
@@ -191,41 +340,45 @@ if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
191
340
  @ds = POSTGRES_DB[:items]
192
341
  end
193
342
  after do
194
- POSTGRES_DB.drop_table(:items)
343
+ POSTGRES_DB.drop_table?(:items)
195
344
  POSTGRES_DB.disconnect
196
345
  end
197
-
346
+
198
347
  specify "should handle FOR UPDATE" do
199
348
  @ds.insert(:number=>20)
200
- c = nil
201
- t = nil
349
+ c, t = nil, nil
350
+ q = Queue.new
202
351
  POSTGRES_DB.transaction do
203
352
  @ds.for_update.first(:id=>1)
204
353
  t = Thread.new do
205
354
  POSTGRES_DB.transaction do
355
+ q.push nil
206
356
  @ds.filter(:id=>1).update(:name=>'Jim')
207
357
  c = @ds.first(:id=>1)
358
+ q.push nil
208
359
  end
209
360
  end
210
- sleep 0.01
361
+ q.pop
211
362
  @ds.filter(:id=>1).update(:number=>30)
212
363
  end
364
+ q.pop
213
365
  t.join
214
366
  c.should == {:id=>1, :number=>30, :name=>'Jim'}
215
367
  end
216
-
368
+
217
369
  specify "should handle FOR SHARE" do
218
370
  @ds.insert(:number=>20)
219
- c = nil
220
- t = nil
371
+ c, t = nil
372
+ q = Queue.new
221
373
  POSTGRES_DB.transaction do
222
374
  @ds.for_share.first(:id=>1)
223
375
  t = Thread.new do
224
376
  POSTGRES_DB.transaction do
225
377
  c = @ds.for_share.filter(:id=>1).first
378
+ q.push nil
226
379
  end
227
380
  end
228
- sleep 0.1
381
+ q.pop
229
382
  @ds.filter(:id=>1).update(:name=>'Jim')
230
383
  c.should == {:id=>1, :number=>20, :name=>nil}
231
384
  end
@@ -235,41 +388,93 @@ if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
235
388
  end
236
389
 
237
390
  describe "A PostgreSQL dataset with a timestamp field" do
391
+ before(:all) do
392
+ @db = POSTGRES_DB
393
+ @db.create_table! :test3 do
394
+ integer :value
395
+ timestamp :time
396
+ end
397
+ @d = @db[:test3]
398
+ end
238
399
  before do
239
- @d = POSTGRES_DB[:test3]
240
400
  @d.delete
241
401
  end
402
+ after do
403
+ @db.convert_infinite_timestamps = false if @db.adapter_scheme == :postgres
404
+ end
405
+ after(:all) do
406
+ @db.drop_table?(:test3)
407
+ end
242
408
 
243
- cspecify "should store milliseconds in time fields for Time objects", :do do
409
+ cspecify "should store milliseconds in time fields for Time objects", :do, :swift do
244
410
  t = Time.now
245
411
  @d << {:value=>1, :time=>t}
246
- t2 = @d[:value =>'1'][:time]
412
+ t2 = @d[:value =>1][:time]
247
413
  @d.literal(t2).should == @d.literal(t)
248
414
  t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
249
- t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000 == t.usec
415
+ (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.usec
250
416
  end
251
417
 
252
- cspecify "should store milliseconds in time fields for DateTime objects", :do do
418
+ cspecify "should store milliseconds in time fields for DateTime objects", :do, :swift do
253
419
  t = DateTime.now
254
420
  @d << {:value=>1, :time=>t}
255
- t2 = @d[:value =>'1'][:time]
421
+ t2 = @d[:value =>1][:time]
256
422
  @d.literal(t2).should == @d.literal(t)
257
423
  t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
258
- t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000 == t.strftime('%N').to_i/1000
424
+ (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.strftime('%N').to_i/1000
425
+ end
426
+
427
+ if POSTGRES_DB.adapter_scheme == :postgres
428
+ specify "should handle infinite timestamps if convert_infinite_timestamps is set" do
429
+ @d << {:time=>Sequel.cast('infinity', :timestamp)}
430
+ @db.convert_infinite_timestamps = :nil
431
+ @db[:test3].get(:time).should == nil
432
+ @db.convert_infinite_timestamps = :string
433
+ @db[:test3].get(:time).should == 'infinity'
434
+ @db.convert_infinite_timestamps = :float
435
+ @db[:test3].get(:time).should == 1.0/0.0
436
+
437
+ @d.update(:time=>Sequel.cast('-infinity', :timestamp))
438
+ @db.convert_infinite_timestamps = :nil
439
+ @db[:test3].get(:time).should == nil
440
+ @db.convert_infinite_timestamps = :string
441
+ @db[:test3].get(:time).should == '-infinity'
442
+ @db.convert_infinite_timestamps = :float
443
+ @db[:test3].get(:time).should == -1.0/0.0
444
+ end
445
+
446
+ specify "should handle conversions from infinite strings/floats in models" do
447
+ c = Class.new(Sequel::Model(:test3))
448
+ @db.convert_infinite_timestamps = :float
449
+ c.new(:time=>'infinity').time.should == 'infinity'
450
+ c.new(:time=>'-infinity').time.should == '-infinity'
451
+ c.new(:time=>1.0/0.0).time.should == 1.0/0.0
452
+ c.new(:time=>-1.0/0.0).time.should == -1.0/0.0
453
+ end
259
454
  end
260
- end
261
455
 
262
- describe "PostgreSQL's EXPLAIN and ANALYZE" do
263
- specify "should not raise errors" do
456
+ specify "explain and analyze should not raise errors" do
264
457
  @d = POSTGRES_DB[:test3]
265
458
  proc{@d.explain}.should_not raise_error
266
459
  proc{@d.analyze}.should_not raise_error
267
460
  end
461
+
462
+ specify "#locks should be a dataset returning database locks " do
463
+ @db.locks.should be_a_kind_of(Sequel::Dataset)
464
+ @db.locks.all.should be_a_kind_of(Array)
465
+ end
268
466
  end
269
467
 
270
468
  describe "A PostgreSQL database" do
271
469
  before do
272
470
  @db = POSTGRES_DB
471
+ @db.create_table! :test2 do
472
+ text :name
473
+ integer :value
474
+ end
475
+ end
476
+ after do
477
+ @db.drop_table?(:test2)
273
478
  end
274
479
 
275
480
  specify "should support column operations" do
@@ -281,12 +486,12 @@ describe "A PostgreSQL database" do
281
486
  @db[:test2].columns.should == [:name, :value, :xyz]
282
487
  @db[:test2] << {:name => 'mmm', :value => 111}
283
488
  @db[:test2].first[:xyz].should == '000'
284
-
489
+
285
490
  @db[:test2].columns.should == [:name, :value, :xyz]
286
491
  @db.drop_column :test2, :xyz
287
-
492
+
288
493
  @db[:test2].columns.should == [:name, :value]
289
-
494
+
290
495
  @db[:test2].delete
291
496
  @db.add_column :test2, :xyz, :text, :default => '000'
292
497
  @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
@@ -295,31 +500,26 @@ describe "A PostgreSQL database" do
295
500
  @db.rename_column :test2, :xyz, :zyx
296
501
  @db[:test2].columns.should == [:name, :value, :zyx]
297
502
  @db[:test2].first[:zyx].should == 'qqqq'
298
-
503
+
299
504
  @db.add_column :test2, :xyz, :float
300
505
  @db[:test2].delete
301
506
  @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
302
507
  @db.set_column_type :test2, :xyz, :integer
303
-
508
+
304
509
  @db[:test2].first[:xyz].should == 57
305
510
  end
306
-
307
- specify "#locks should be a dataset returning database locks " do
308
- @db.locks.should be_a_kind_of(Sequel::Dataset)
309
- @db.locks.all.should be_a_kind_of(Array)
310
- end
311
- end
511
+ end
312
512
 
313
513
  describe "A PostgreSQL database" do
314
514
  before do
315
515
  @db = POSTGRES_DB
316
- @db.drop_table(:posts) rescue nil
516
+ @db.drop_table?(:posts)
317
517
  @db.sqls.clear
318
518
  end
319
519
  after do
320
- @db.drop_table(:posts) rescue nil
520
+ @db.drop_table?(:posts)
321
521
  end
322
-
522
+
323
523
  specify "should support resetting the primary key sequence" do
324
524
  @db.create_table(:posts){primary_key :a}
325
525
  @db[:posts].insert(:a=>20).should == 20
@@ -330,7 +530,7 @@ describe "A PostgreSQL database" do
330
530
  @db[:posts].insert.should == 21
331
531
  @db[:posts].order(:a).map(:a).should == [1, 2, 10, 20, 21]
332
532
  end
333
-
533
+
334
534
  specify "should support specifying Integer/Bignum/Fixnum types in primary keys and have them be auto incrementing" do
335
535
  @db.create_table(:posts){primary_key :a, :type=>Integer}
336
536
  @db[:posts].insert.should == 1
@@ -347,22 +547,26 @@ describe "A PostgreSQL database" do
347
547
  @db.create_table(:posts){Integer :a}
348
548
  @db.reset_primary_key_sequence(:posts).should == nil
349
549
  end
350
-
550
+
351
551
  specify "should support opclass specification" do
352
552
  @db.create_table(:posts){text :title; text :body; integer :user_id; index(:user_id, :opclass => :int4_ops, :type => :btree)}
353
- @db.sqls.should == [
354
- "CREATE TABLE posts (title text, body text, user_id integer)",
355
- "CREATE INDEX posts_user_id_index ON posts USING btree (user_id int4_ops)"
356
- ]
553
+ check_sqls do
554
+ @db.sqls.should == [
555
+ 'CREATE TABLE "posts" ("title" text, "body" text, "user_id" integer)',
556
+ 'CREATE INDEX "posts_user_id_index" ON "posts" USING btree ("user_id" int4_ops)'
557
+ ]
558
+ end
357
559
  end
358
560
 
359
561
  specify "should support fulltext indexes and searching" do
360
562
  @db.create_table(:posts){text :title; text :body; full_text_index [:title, :body]; full_text_index :title, :language => 'french'}
361
- @db.sqls.should == [
362
- "CREATE TABLE posts (title text, body text)",
363
- "CREATE INDEX posts_title_body_index ON posts USING gin (to_tsvector('simple', (COALESCE(title, '') || ' ' || COALESCE(body, ''))))",
364
- "CREATE INDEX posts_title_index ON posts USING gin (to_tsvector('french', (COALESCE(title, ''))))"
365
- ]
563
+ check_sqls do
564
+ @db.sqls.should == [
565
+ %{CREATE TABLE "posts" ("title" text, "body" text)},
566
+ %{CREATE INDEX "posts_title_body_index" ON "posts" USING gin (to_tsvector('simple'::regconfig, (COALESCE("title", '') || ' ' || COALESCE("body", ''))))},
567
+ %{CREATE INDEX "posts_title_index" ON "posts" USING gin (to_tsvector('french'::regconfig, (COALESCE("title", ''))))}
568
+ ]
569
+ end
366
570
 
367
571
  @db[:posts].insert(:title=>'ruby rails', :body=>'yowsa')
368
572
  @db[:posts].insert(:title=>'sequel', :body=>'ruby')
@@ -372,52 +576,67 @@ describe "A PostgreSQL database" do
372
576
  @db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'yowsa'}]
373
577
  @db[:posts].full_text_search([:title, :body], ['yowsa', 'rails']).all.should == [:title=>'ruby rails', :body=>'yowsa']
374
578
  @db[:posts].full_text_search(:title, 'scooby', :language => 'french').all.should == [{:title=>'ruby scooby', :body=>'x'}]
375
- @db.sqls.should == [
376
- "SELECT * FROM posts WHERE (to_tsvector('simple', (COALESCE(title, ''))) @@ to_tsquery('simple', 'rails'))",
377
- "SELECT * FROM posts WHERE (to_tsvector('simple', (COALESCE(title, '') || ' ' || COALESCE(body, ''))) @@ to_tsquery('simple', 'yowsa | rails'))",
378
- "SELECT * FROM posts WHERE (to_tsvector('french', (COALESCE(title, ''))) @@ to_tsquery('french', 'scooby'))"]
579
+ check_sqls do
580
+ @db.sqls.should == [
581
+ %{SELECT * FROM "posts" WHERE (to_tsvector('simple'::regconfig, (COALESCE("title", ''))) @@ to_tsquery('simple'::regconfig, 'rails'))},
582
+ %{SELECT * FROM "posts" WHERE (to_tsvector('simple'::regconfig, (COALESCE("title", '') || ' ' || COALESCE("body", ''))) @@ to_tsquery('simple'::regconfig, 'yowsa | rails'))},
583
+ %{SELECT * FROM "posts" WHERE (to_tsvector('french'::regconfig, (COALESCE("title", ''))) @@ to_tsquery('french'::regconfig, 'scooby'))}]
584
+ end
585
+
586
+ @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
587
+ @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
379
588
  end
380
589
 
381
590
  specify "should support spatial indexes" do
382
591
  @db.create_table(:posts){box :geom; spatial_index [:geom]}
383
- @db.sqls.should == [
384
- "CREATE TABLE posts (geom box)",
385
- "CREATE INDEX posts_geom_index ON posts USING gist (geom)"
386
- ]
592
+ check_sqls do
593
+ @db.sqls.should == [
594
+ 'CREATE TABLE "posts" ("geom" box)',
595
+ 'CREATE INDEX "posts_geom_index" ON "posts" USING gist ("geom")'
596
+ ]
597
+ end
387
598
  end
388
-
599
+
389
600
  specify "should support indexes with index type" do
390
601
  @db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'hash'}
391
- @db.sqls.should == [
392
- "CREATE TABLE posts (title varchar(5))",
393
- "CREATE INDEX posts_title_index ON posts USING hash (title)"
394
- ]
602
+ check_sqls do
603
+ @db.sqls.should == [
604
+ 'CREATE TABLE "posts" ("title" varchar(5))',
605
+ 'CREATE INDEX "posts_title_index" ON "posts" USING hash ("title")'
606
+ ]
607
+ end
395
608
  end
396
-
609
+
397
610
  specify "should support unique indexes with index type" do
398
611
  @db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'btree', :unique => true}
399
- @db.sqls.should == [
400
- "CREATE TABLE posts (title varchar(5))",
401
- "CREATE UNIQUE INDEX posts_title_index ON posts USING btree (title)"
402
- ]
612
+ check_sqls do
613
+ @db.sqls.should == [
614
+ 'CREATE TABLE "posts" ("title" varchar(5))',
615
+ 'CREATE UNIQUE INDEX "posts_title_index" ON "posts" USING btree ("title")'
616
+ ]
617
+ end
403
618
  end
404
-
619
+
405
620
  specify "should support partial indexes" do
406
621
  @db.create_table(:posts){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
407
- @db.sqls.should == [
408
- "CREATE TABLE posts (title varchar(5))",
409
- "CREATE INDEX posts_title_index ON posts (title) WHERE (title = '5')"
410
- ]
622
+ check_sqls do
623
+ @db.sqls.should == [
624
+ 'CREATE TABLE "posts" ("title" varchar(5))',
625
+ 'CREATE INDEX "posts_title_index" ON "posts" ("title") WHERE ("title" = \'5\')'
626
+ ]
627
+ end
411
628
  end
412
-
629
+
413
630
  specify "should support identifiers for table names in indicies" do
414
631
  @db.create_table(Sequel::SQL::Identifier.new(:posts)){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
415
- @db.sqls.should == [
416
- "CREATE TABLE posts (title varchar(5))",
417
- "CREATE INDEX posts_title_index ON posts (title) WHERE (title = '5')"
418
- ]
632
+ check_sqls do
633
+ @db.sqls.should == [
634
+ 'CREATE TABLE "posts" ("title" varchar(5))',
635
+ 'CREATE INDEX "posts_title_index" ON "posts" ("title") WHERE ("title" = \'5\')'
636
+ ]
637
+ end
419
638
  end
420
-
639
+
421
640
  specify "should support renaming tables" do
422
641
  @db.create_table!(:posts1){primary_key :a}
423
642
  @db.rename_table(:posts1, :posts)
@@ -427,38 +646,35 @@ end
427
646
  describe "Postgres::Dataset#import" do
428
647
  before do
429
648
  @db = POSTGRES_DB
430
- @db.create_table!(:test){Integer :x; Integer :y}
649
+ @db.create_table!(:test){primary_key :x; Integer :y}
431
650
  @db.sqls.clear
432
651
  @ds = @db[:test]
433
652
  end
434
653
  after do
435
- @db.drop_table(:test) rescue nil
654
+ @db.drop_table?(:test)
436
655
  end
437
-
438
- specify "#import should return separate insert statements if server_version < 80200" do
439
- @ds.meta_def(:server_version){80199}
440
-
656
+
657
+
658
+ specify "#import should a single insert statement" do
441
659
  @ds.import([:x, :y], [[1, 2], [3, 4]])
442
-
443
- @db.sqls.should == [
444
- 'BEGIN',
445
- 'INSERT INTO test (x, y) VALUES (1, 2)',
446
- 'INSERT INTO test (x, y) VALUES (3, 4)',
447
- 'COMMIT'
448
- ]
660
+ check_sqls do
661
+ @db.sqls.should == ['BEGIN', 'INSERT INTO "test" ("x", "y") VALUES (1, 2), (3, 4)', 'COMMIT']
662
+ end
449
663
  @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
450
664
  end
451
-
452
- specify "#import should a single insert statement if server_version >= 80200" do
453
- @ds.meta_def(:server_version){80200}
454
-
455
- @ds.import([:x, :y], [[1, 2], [3, 4]])
456
-
457
- @db.sqls.should == [
458
- 'BEGIN',
459
- 'INSERT INTO test (x, y) VALUES (1, 2), (3, 4)',
460
- 'COMMIT'
461
- ]
665
+
666
+ specify "#import should work correctly when returning primary keys" do
667
+ @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key).should == [1, 3]
668
+ @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
669
+ end
670
+
671
+ specify "#import should work correctly when returning primary keys with :slice option" do
672
+ @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key, :slice=>1).should == [1, 3]
673
+ @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
674
+ end
675
+
676
+ specify "#import should work correctly with an arbitrary returning value" do
677
+ @ds.returning(:y, :x).import([:x, :y], [[1, 2], [3, 4]]).should == [{:y=>2, :x=>1}, {:y=>4, :x=>3}]
462
678
  @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
463
679
  end
464
680
  end
@@ -471,7 +687,7 @@ describe "Postgres::Dataset#insert" do
471
687
  @ds = @db[:test5]
472
688
  end
473
689
  after do
474
- @db.drop_table(:test5) rescue nil
690
+ @db.drop_table?(:test5)
475
691
  end
476
692
 
477
693
  specify "should work with static SQL" do
@@ -480,56 +696,19 @@ describe "Postgres::Dataset#insert" do
480
696
  @ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}]
481
697
  end
482
698
 
483
- specify "should work regardless of how it is used" do
484
- @ds.insert(:value=>10).should == 1
485
- @ds.disable_insert_returning.insert(:value=>20).should == 2
486
- @ds.meta_def(:server_version){80100}
487
- @ds.insert(:value=>13).should == 3
488
-
489
- @db.sqls.reject{|x| x =~ /pg_class/}.should == [
490
- 'INSERT INTO test5 (value) VALUES (10) RETURNING xid',
491
- 'INSERT INTO test5 (value) VALUES (20)',
492
- "SELECT currval('\"public\".test5_xid_seq')",
493
- 'INSERT INTO test5 (value) VALUES (13)',
494
- "SELECT currval('\"public\".test5_xid_seq')"
495
- ]
496
- @ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}, {:xid=>3, :value=>13}]
497
- end
498
-
499
- specify "should call execute_insert if server_version < 80200" do
500
- @ds.meta_def(:server_version){80100}
501
- @ds.should_receive(:execute_insert).once.with('INSERT INTO test5 (value) VALUES (10)', :table=>:test5, :values=>{:value=>10})
502
- @ds.insert(:value=>10)
503
- end
504
-
505
- specify "should call execute_insert if disabling insert returning" do
506
- @ds.disable_insert_returning!
507
- @ds.should_receive(:execute_insert).once.with('INSERT INTO test5 (value) VALUES (10)', :table=>:test5, :values=>{:value=>10})
508
- @ds.insert(:value=>10)
509
- end
510
-
511
- specify "should use INSERT RETURNING if server_version >= 80200" do
512
- @ds.meta_def(:server_version){80201}
513
- @ds.insert(:value=>10)
514
- @db.sqls.last.should == 'INSERT INTO test5 (value) VALUES (10) RETURNING xid'
515
- end
516
-
517
- specify "should have insert_returning_sql use the RETURNING keyword" do
518
- @ds.insert_returning_sql(:xid, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING xid"
519
- @ds.insert_returning_sql('*'.lit, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING *"
520
- end
521
-
522
- specify "should have insert_select return nil if server_version < 80200" do
523
- @ds.meta_def(:server_version){80100}
524
- @ds.insert_select(:value=>10).should == nil
699
+ specify "should insert correctly if using a column array and a value array" do
700
+ @ds.insert([:value], [10]).should == 1
701
+ @ds.all.should == [{:xid=>1, :value=>10}]
525
702
  end
526
703
 
527
- specify "should have insert_select return nil if disable_insert_returning is used" do
528
- @ds.disable_insert_returning.insert_select(:value=>10).should == nil
704
+ specify "should use INSERT RETURNING" do
705
+ @ds.insert(:value=>10).should == 1
706
+ check_sqls do
707
+ @db.sqls.last.should == 'INSERT INTO "test5" ("value") VALUES (10) RETURNING "xid"'
708
+ end
529
709
  end
530
710
 
531
- specify "should have insert_select insert the record and return the inserted record if server_version >= 80200" do
532
- @ds.meta_def(:server_version){80201}
711
+ specify "should have insert_select insert the record and return the inserted record" do
533
712
  h = @ds.insert_select(:value=>10)
534
713
  h[:value].should == 10
535
714
  @ds.first(:xid=>h[:xid])[:value].should == 10
@@ -545,9 +724,9 @@ describe "Postgres::Dataset#insert" do
545
724
  end
546
725
 
547
726
  specify "should return nil if the table has no primary key" do
548
- ds = POSTGRES_DB[:test4]
549
- ds.delete
550
- ds.insert(:name=>'a').should == nil
727
+ @db.create_table!(:test5){String :name; Integer :value}
728
+ @ds.delete
729
+ @ds.insert(:name=>'a').should == nil
551
730
  end
552
731
  end
553
732
 
@@ -558,36 +737,35 @@ describe "Postgres::Database schema qualified tables" do
558
737
  POSTGRES_DB.instance_variable_set(:@primary_key_sequences, {})
559
738
  end
560
739
  after do
561
- POSTGRES_DB.quote_identifiers = false
562
740
  POSTGRES_DB << "DROP SCHEMA schema_test CASCADE"
563
741
  POSTGRES_DB.default_schema = nil
564
742
  end
565
-
743
+
566
744
  specify "should be able to create, drop, select and insert into tables in a given schema" do
567
745
  POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
568
746
  POSTGRES_DB[:schema_test__schema_test].first.should == nil
569
747
  POSTGRES_DB[:schema_test__schema_test].insert(:i=>1).should == 1
570
748
  POSTGRES_DB[:schema_test__schema_test].first.should == {:i=>1}
571
- POSTGRES_DB.from('schema_test.schema_test'.lit).first.should == {:i=>1}
749
+ POSTGRES_DB.from(Sequel.lit('schema_test.schema_test')).first.should == {:i=>1}
572
750
  POSTGRES_DB.drop_table(:schema_test__schema_test)
573
- POSTGRES_DB.create_table(:schema_test.qualify(:schema_test)){integer :i}
751
+ POSTGRES_DB.create_table(Sequel.qualify(:schema_test, :schema_test)){integer :i}
574
752
  POSTGRES_DB[:schema_test__schema_test].first.should == nil
575
- POSTGRES_DB.from('schema_test.schema_test'.lit).first.should == nil
576
- POSTGRES_DB.drop_table(:schema_test.qualify(:schema_test))
753
+ POSTGRES_DB.from(Sequel.lit('schema_test.schema_test')).first.should == nil
754
+ POSTGRES_DB.drop_table(Sequel.qualify(:schema_test, :schema_test))
577
755
  end
578
-
756
+
579
757
  specify "#tables should not include tables in a default non-public schema" do
580
758
  POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
581
759
  POSTGRES_DB.tables.should include(:schema_test)
582
760
  POSTGRES_DB.tables.should_not include(:pg_am)
583
761
  POSTGRES_DB.tables.should_not include(:domain_udt_usage)
584
762
  end
585
-
763
+
586
764
  specify "#tables should return tables in the schema provided by the :schema argument" do
587
765
  POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
588
766
  POSTGRES_DB.tables(:schema=>:schema_test).should == [:schema_test]
589
767
  end
590
-
768
+
591
769
  specify "#schema should not include columns from tables in a default non-public schema" do
592
770
  POSTGRES_DB.create_table(:schema_test__domains){integer :i}
593
771
  sch = POSTGRES_DB.schema(:domains)
@@ -595,7 +773,7 @@ describe "Postgres::Database schema qualified tables" do
595
773
  cs.should include(:i)
596
774
  cs.should_not include(:data_type)
597
775
  end
598
-
776
+
599
777
  specify "#schema should only include columns from the table in the given :schema argument" do
600
778
  POSTGRES_DB.create_table!(:domains){integer :d}
601
779
  POSTGRES_DB.create_table(:schema_test__domains){integer :i}
@@ -605,54 +783,58 @@ describe "Postgres::Database schema qualified tables" do
605
783
  cs.should_not include(:d)
606
784
  POSTGRES_DB.drop_table(:domains)
607
785
  end
608
-
609
- specify "#table_exists? should not include tables from the default non-public schemas" do
610
- POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
611
- POSTGRES_DB.table_exists?(:schema_test).should == true
612
- POSTGRES_DB.table_exists?(:domain_udt_usage).should == false
786
+
787
+ specify "#schema should raise an exception if columns from tables in two separate schema are returned" do
788
+ POSTGRES_DB.create_table!(:public__domains){integer :d}
789
+ POSTGRES_DB.create_table(:schema_test__domains){integer :i}
790
+ begin
791
+ proc{POSTGRES_DB.schema(:domains)}.should raise_error(Sequel::Error)
792
+ POSTGRES_DB.schema(:public__domains).map{|x| x.first}.should == [:d]
793
+ POSTGRES_DB.schema(:schema_test__domains).map{|x| x.first}.should == [:i]
794
+ ensure
795
+ POSTGRES_DB.drop_table?(:public__domains)
796
+ end
613
797
  end
614
-
798
+
615
799
  specify "#table_exists? should see if the table is in a given schema" do
616
800
  POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
617
801
  POSTGRES_DB.table_exists?(:schema_test__schema_test).should == true
618
802
  end
619
-
803
+
620
804
  specify "should be able to get primary keys for tables in a given schema" do
621
805
  POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
622
806
  POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
623
807
  end
624
-
808
+
625
809
  specify "should be able to get serial sequences for tables in a given schema" do
626
810
  POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
627
- POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
811
+ POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
628
812
  end
629
-
813
+
630
814
  specify "should be able to get serial sequences for tables that have spaces in the name in a given schema" do
631
- POSTGRES_DB.quote_identifiers = true
632
815
  POSTGRES_DB.create_table(:"schema_test__schema test"){primary_key :i}
633
816
  POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."schema test_i_seq"'
634
817
  end
635
-
818
+
636
819
  specify "should be able to get custom sequences for tables in a given schema" do
637
820
  POSTGRES_DB << "CREATE SEQUENCE schema_test.kseq"
638
- POSTGRES_DB.create_table(:schema_test__schema_test){integer :j; primary_key :k, :type=>:integer, :default=>"nextval('schema_test.kseq'::regclass)".lit}
821
+ POSTGRES_DB.create_table(:schema_test__schema_test){integer :j; primary_key :k, :type=>:integer, :default=>Sequel.lit("nextval('schema_test.kseq'::regclass)")}
639
822
  POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".kseq'
640
823
  end
641
-
824
+
642
825
  specify "should be able to get custom sequences for tables that have spaces in the name in a given schema" do
643
- POSTGRES_DB.quote_identifiers = true
644
826
  POSTGRES_DB << "CREATE SEQUENCE schema_test.\"ks eq\""
645
- POSTGRES_DB.create_table(:"schema_test__schema test"){integer :j; primary_key :k, :type=>:integer, :default=>"nextval('schema_test.\"ks eq\"'::regclass)".lit}
827
+ POSTGRES_DB.create_table(:"schema_test__schema test"){integer :j; primary_key :k, :type=>:integer, :default=>Sequel.lit("nextval('schema_test.\"ks eq\"'::regclass)")}
646
828
  POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."ks eq"'
647
829
  end
648
-
830
+
649
831
  specify "#default_schema= should change the default schema used from public" do
650
832
  POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
651
833
  POSTGRES_DB.default_schema = :schema_test
652
834
  POSTGRES_DB.table_exists?(:schema_test).should == true
653
835
  POSTGRES_DB.tables.should == [:schema_test]
654
836
  POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
655
- POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
837
+ POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
656
838
  end
657
839
  end
658
840
 
@@ -661,13 +843,12 @@ describe "Postgres::Database schema qualified tables and eager graphing" do
661
843
  @db = POSTGRES_DB
662
844
  @db.run "DROP SCHEMA s CASCADE" rescue nil
663
845
  @db.run "CREATE SCHEMA s"
664
- @db.quote_identifiers = true
665
-
846
+
666
847
  @db.create_table(:s__bands){primary_key :id; String :name}
667
848
  @db.create_table(:s__albums){primary_key :id; String :name; foreign_key :band_id, :s__bands}
668
849
  @db.create_table(:s__tracks){primary_key :id; String :name; foreign_key :album_id, :s__albums}
669
850
  @db.create_table(:s__members){primary_key :id; String :name; foreign_key :band_id, :s__bands}
670
-
851
+
671
852
  @Band = Class.new(Sequel::Model(:s__bands))
672
853
  @Album = Class.new(Sequel::Model(:s__albums))
673
854
  @Track = Class.new(Sequel::Model(:s__tracks))
@@ -676,17 +857,17 @@ describe "Postgres::Database schema qualified tables and eager graphing" do
676
857
  def @Album.name; :Album; end
677
858
  def @Track.name; :Track; end
678
859
  def @Member.name; :Member; end
679
-
860
+
680
861
  @Band.one_to_many :albums, :class=>@Album, :order=>:name
681
862
  @Band.one_to_many :members, :class=>@Member, :order=>:name
682
863
  @Album.many_to_one :band, :class=>@Band, :order=>:name
683
864
  @Album.one_to_many :tracks, :class=>@Track, :order=>:name
684
865
  @Track.many_to_one :album, :class=>@Album, :order=>:name
685
866
  @Member.many_to_one :band, :class=>@Band, :order=>:name
686
-
867
+
687
868
  @Member.many_to_many :members, :class=>@Member, :join_table=>:s__bands, :right_key=>:id, :left_key=>:id, :left_primary_key=>:band_id, :right_primary_key=>:band_id, :order=>:name
688
869
  @Band.many_to_many :tracks, :class=>@Track, :join_table=>:s__albums, :right_key=>:id, :right_primary_key=>:album_id, :order=>:name
689
-
870
+
690
871
  @b1 = @Band.create(:name=>"BM")
691
872
  @b2 = @Band.create(:name=>"J")
692
873
  @a1 = @Album.create(:name=>"BM1", :band=>@b1)
@@ -703,10 +884,9 @@ describe "Postgres::Database schema qualified tables and eager graphing" do
703
884
  @m4 = @Member.create(:name=>"JC", :band=>@b2)
704
885
  end
705
886
  after(:all) do
706
- @db.quote_identifiers = false
707
887
  @db.run "DROP SCHEMA s CASCADE"
708
888
  end
709
-
889
+
710
890
  specify "should return all eager graphs correctly" do
711
891
  bands = @Band.order(:bands__name).eager_graph(:albums).all
712
892
  bands.should == [@b1, @b2]
@@ -725,14 +905,14 @@ describe "Postgres::Database schema qualified tables and eager graphing" do
725
905
  end
726
906
 
727
907
  specify "should have eager graphs work with previous joins" do
728
- bands = @Band.order(:bands__name).select(:s__bands.*).join(:s__members, :band_id=>:id).from_self(:alias=>:bands0).eager_graph(:albums=>:tracks).all
908
+ bands = @Band.order(:bands__name).select_all(:s__bands).join(:s__members, :band_id=>:id).from_self(:alias=>:bands0).eager_graph(:albums=>:tracks).all
729
909
  bands.should == [@b1, @b2]
730
910
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
731
911
  bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
732
912
  end
733
913
 
734
914
  specify "should have eager graphs work with joins with the same tables" do
735
- bands = @Band.order(:bands__name).select(:s__bands.*).join(:s__members, :band_id=>:id).eager_graph({:albums=>:tracks}, :members).all
915
+ bands = @Band.order(:bands__name).select_all(:s__bands).join(:s__members, :band_id=>:id).eager_graph({:albums=>:tracks}, :members).all
736
916
  bands.should == [@b1, @b2]
737
917
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
738
918
  bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
@@ -770,62 +950,62 @@ describe "Postgres::Database schema qualified tables and eager graphing" do
770
950
  bands.should == [@b1, @b2]
771
951
  bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
772
952
 
773
- bands = @Band.order(:tracks__name).from(:s__bands.as(:tracks)).eager_graph(:tracks).all
953
+ bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as(:tracks)).eager_graph(:tracks).all
774
954
  bands.should == [@b1, @b2]
775
955
  bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
776
956
 
777
- bands = @Band.order(:tracks__name).from(:s__bands.as(:tracks.identifier)).eager_graph(:tracks).all
957
+ bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as(Sequel.identifier(:tracks))).eager_graph(:tracks).all
778
958
  bands.should == [@b1, @b2]
779
959
  bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
780
960
 
781
- bands = @Band.order(:tracks__name).from(:s__bands.as('tracks')).eager_graph(:tracks).all
961
+ bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as('tracks')).eager_graph(:tracks).all
782
962
  bands.should == [@b1, @b2]
783
963
  bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
784
964
  end
785
965
 
786
966
  specify "should have eager graphs work with join tables with aliases" do
787
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums___tracks, :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
967
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums___tracks, :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
788
968
  bands.should == [@b1, @b2]
789
969
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
790
970
  bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
791
971
 
792
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as(:tracks), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
972
+ bands = @Band.order(:bands__name).eager_graph(:members).join(Sequel.as(:s__albums, :tracks), :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
793
973
  bands.should == [@b1, @b2]
794
974
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
795
975
  bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
796
976
 
797
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as('tracks'), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
977
+ bands = @Band.order(:bands__name).eager_graph(:members).join(Sequel.as(:s__albums, 'tracks'), :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
798
978
  bands.should == [@b1, @b2]
799
979
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
800
980
  bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
801
981
 
802
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as(:tracks.identifier), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
982
+ bands = @Band.order(:bands__name).eager_graph(:members).join(Sequel.as(:s__albums, Sequel.identifier(:tracks)), :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
803
983
  bands.should == [@b1, @b2]
804
984
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
805
985
  bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
806
986
 
807
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>:tracks).eager_graph(:albums=>:tracks).all
987
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>Sequel.qualify(:s__bands, :id)}, :table_alias=>:tracks).eager_graph(:albums=>:tracks).all
808
988
  bands.should == [@b1, @b2]
809
989
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
810
990
  bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
811
991
 
812
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>'tracks').eager_graph(:albums=>:tracks).all
992
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>Sequel.qualify(:s__bands, :id)}, :table_alias=>'tracks').eager_graph(:albums=>:tracks).all
813
993
  bands.should == [@b1, @b2]
814
994
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
815
995
  bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
816
996
 
817
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>:tracks.identifier).eager_graph(:albums=>:tracks).all
997
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>Sequel.qualify(:s__bands, :id)}, :table_alias=>Sequel.identifier(:tracks)).eager_graph(:albums=>:tracks).all
818
998
  bands.should == [@b1, @b2]
819
999
  bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
820
1000
  bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
821
1001
  end
822
1002
 
823
1003
  specify "should have eager graphs work with different types of qualified from tables" do
824
- bands = @Band.order(:bands__name).from(:bands.qualify(:s)).eager_graph(:tracks).all
1004
+ bands = @Band.order(:bands__name).from(Sequel.qualify(:s, :bands)).eager_graph(:tracks).all
825
1005
  bands.should == [@b1, @b2]
826
1006
  bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
827
1007
 
828
- bands = @Band.order(:bands__name).from(:bands.identifier.qualify(:s)).eager_graph(:tracks).all
1008
+ bands = @Band.order(:bands__name).from(Sequel.identifier(:bands).qualify(:s)).eager_graph(:tracks).all
829
1009
  bands.should == [@b1, @b2]
830
1010
  bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
831
1011
 
@@ -837,20 +1017,21 @@ describe "Postgres::Database schema qualified tables and eager graphing" do
837
1017
  end
838
1018
 
839
1019
  if POSTGRES_DB.server_version >= 80300
840
-
841
- POSTGRES_DB.create_table! :test6 do
842
- text :title
843
- text :body
844
- full_text_index [:title, :body]
845
- end
846
-
847
1020
  describe "PostgreSQL tsearch2" do
848
- before do
1021
+ before(:all) do
1022
+ POSTGRES_DB.create_table! :test6 do
1023
+ text :title
1024
+ text :body
1025
+ full_text_index [:title, :body]
1026
+ end
849
1027
  @ds = POSTGRES_DB[:test6]
850
1028
  end
851
1029
  after do
852
1030
  POSTGRES_DB[:test6].delete
853
1031
  end
1032
+ after(:all) do
1033
+ POSTGRES_DB.drop_table?(:test6)
1034
+ end
854
1035
 
855
1036
  specify "should search by indexed column" do
856
1037
  record = {:title => "oopsla conference", :body => "test"}
@@ -886,9 +1067,9 @@ if POSTGRES_DB.dataset.supports_window_functions?
886
1067
  @ds.insert(:id=>6, :group_id=>2, :amount=>100000)
887
1068
  end
888
1069
  after do
889
- @db.drop_table(:i1)
1070
+ @db.drop_table?(:i1)
890
1071
  end
891
-
1072
+
892
1073
  specify "should give correct results for window functions" do
893
1074
  @ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:over, :args=>amount, :window=>win){}}.all.should ==
894
1075
  [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
@@ -902,15 +1083,16 @@ if POSTGRES_DB.dataset.supports_window_functions?
902
1083
  end
903
1084
  end
904
1085
 
905
- describe "Postgres::Database functions, languages, and triggers" do
1086
+ describe "Postgres::Database functions, languages, schemas, and triggers" do
906
1087
  before do
907
1088
  @d = POSTGRES_DB
908
1089
  end
909
1090
  after do
910
1091
  @d.drop_function('tf', :if_exists=>true, :cascade=>true)
911
1092
  @d.drop_function('tf', :if_exists=>true, :cascade=>true, :args=>%w'integer integer')
912
- @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
913
- @d.drop_table(:test) rescue nil
1093
+ @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
1094
+ @d.drop_schema(:sequel, :if_exists=>true, :cascade=>true)
1095
+ @d.drop_table?(:test)
914
1096
  end
915
1097
 
916
1098
  specify "#create_function and #drop_function should create and drop functions" do
@@ -923,7 +1105,7 @@ describe "Postgres::Database functions, languages, and triggers" do
923
1105
  @d.drop_function('tf')
924
1106
  proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
925
1107
  end
926
-
1108
+
927
1109
  specify "#create_function and #drop_function should support options" do
928
1110
  args = ['tf', 'SELECT $1 + $2', {:args=>[[:integer, :a], :integer], :replace=>true, :returns=>:integer, :language=>'SQL', :behavior=>:immutable, :strict=>true, :security_definer=>true, :cost=>2, :set=>{:search_path => 'public'}}]
929
1111
  @d.send(:create_function_sql,*args).should =~ /\A\s*CREATE OR REPLACE FUNCTION tf\(a integer, integer\)\s+RETURNS integer\s+LANGUAGE SQL\s+IMMUTABLE\s+STRICT\s+SECURITY DEFINER\s+COST 2\s+SET search_path = public\s+AS 'SELECT \$1 \+ \$2'\s*\z/
@@ -937,24 +1119,33 @@ describe "Postgres::Database functions, languages, and triggers" do
937
1119
  # Make sure if exists works
938
1120
  @d.drop_function(*args)
939
1121
  end
940
-
1122
+
941
1123
  specify "#create_language and #drop_language should create and drop languages" do
942
1124
  @d.send(:create_language_sql, :plpgsql).should == 'CREATE LANGUAGE plpgsql'
943
- @d.create_language(:plpgsql, :replace=>true)
1125
+ @d.create_language(:plpgsql, :replace=>true) if @d.server_version < 90000
944
1126
  proc{@d.create_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
945
1127
  @d.send(:drop_language_sql, :plpgsql).should == 'DROP LANGUAGE plpgsql'
946
- @d.drop_language(:plpgsql)
947
- proc{@d.drop_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
1128
+ @d.drop_language(:plpgsql) if @d.server_version < 90000
1129
+ proc{@d.drop_language(:plpgsql)}.should raise_error(Sequel::DatabaseError) if @d.server_version < 90000
948
1130
  @d.send(:create_language_sql, :plpgsql, :replace=>true, :trusted=>true, :handler=>:a, :validator=>:b).should == (@d.server_version >= 90000 ? 'CREATE OR REPLACE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b' : 'CREATE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b')
949
1131
  @d.send(:drop_language_sql, :plpgsql, :if_exists=>true, :cascade=>true).should == 'DROP LANGUAGE IF EXISTS plpgsql CASCADE'
950
1132
  # Make sure if exists works
951
- @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
1133
+ @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
1134
+ end
1135
+
1136
+ specify "#create_schema and #drop_schema should create and drop schemas" do
1137
+ @d.send(:create_schema_sql, :sequel).should == 'CREATE SCHEMA "sequel"'
1138
+ @d.send(:drop_schema_sql, :sequel).should == 'DROP SCHEMA "sequel"'
1139
+ @d.send(:drop_schema_sql, :sequel, :if_exists=>true, :cascade=>true).should == 'DROP SCHEMA IF EXISTS "sequel" CASCADE'
1140
+ @d.create_schema(:sequel)
1141
+ @d.create_table(:sequel__test){Integer :a}
1142
+ @d.drop_schema(:sequel, :if_exists=>true, :cascade=>true)
952
1143
  end
953
-
1144
+
954
1145
  specify "#create_trigger and #drop_trigger should create and drop triggers" do
955
- @d.create_language(:plpgsql)
1146
+ @d.create_language(:plpgsql) if @d.server_version < 90000
956
1147
  @d.create_function(:tf, 'BEGIN IF NEW.value IS NULL THEN RAISE EXCEPTION \'Blah\'; END IF; RETURN NEW; END;', :language=>:plpgsql, :returns=>:trigger)
957
- @d.send(:create_trigger_sql, :test, :identity, :tf, :each_row=>true).should == 'CREATE TRIGGER identity BEFORE INSERT OR UPDATE OR DELETE ON test FOR EACH ROW EXECUTE PROCEDURE tf()'
1148
+ @d.send(:create_trigger_sql, :test, :identity, :tf, :each_row=>true).should == 'CREATE TRIGGER identity BEFORE INSERT OR UPDATE OR DELETE ON "test" FOR EACH ROW EXECUTE PROCEDURE tf()'
958
1149
  @d.create_table(:test){String :name; Integer :value}
959
1150
  @d.create_trigger(:test, :identity, :tf, :each_row=>true)
960
1151
  @d[:test].insert(:name=>'a', :value=>1)
@@ -963,41 +1154,45 @@ describe "Postgres::Database functions, languages, and triggers" do
963
1154
  @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>1}]
964
1155
  @d[:test].filter(:name=>'a').update(:value=>3)
965
1156
  @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>3}]
966
- @d.send(:drop_trigger_sql, :test, :identity).should == 'DROP TRIGGER identity ON test'
1157
+ @d.send(:drop_trigger_sql, :test, :identity).should == 'DROP TRIGGER identity ON "test"'
967
1158
  @d.drop_trigger(:test, :identity)
968
- @d.send(:create_trigger_sql, :test, :identity, :tf, :after=>true, :events=>:insert, :args=>[1, 'a']).should == 'CREATE TRIGGER identity AFTER INSERT ON test EXECUTE PROCEDURE tf(1, \'a\')'
969
- @d.send(:drop_trigger_sql, :test, :identity, :if_exists=>true, :cascade=>true).should == 'DROP TRIGGER IF EXISTS identity ON test CASCADE'
1159
+ @d.send(:create_trigger_sql, :test, :identity, :tf, :after=>true, :events=>:insert, :args=>[1, 'a']).should == 'CREATE TRIGGER identity AFTER INSERT ON "test" EXECUTE PROCEDURE tf(1, \'a\')'
1160
+ @d.send(:drop_trigger_sql, :test, :identity, :if_exists=>true, :cascade=>true).should == 'DROP TRIGGER IF EXISTS identity ON "test" CASCADE'
970
1161
  # Make sure if exists works
971
1162
  @d.drop_trigger(:test, :identity, :if_exists=>true, :cascade=>true)
972
1163
  end
973
1164
  end
974
1165
 
975
1166
  if POSTGRES_DB.adapter_scheme == :postgres
976
- describe "Postgres::Dataset #use_cursor" do
977
- before(:all) do
978
- @db = POSTGRES_DB
979
- @db.create_table!(:test_cursor){Integer :x}
980
- @db.sqls.clear
981
- @ds = @db[:test_cursor]
982
- @db.transaction{1001.times{|i| @ds.insert(i)}}
983
- end
984
- after(:all) do
985
- @db.drop_table(:test) rescue nil
986
- end
987
-
1167
+ describe "Postgres::Dataset #use_cursor" do
1168
+ before(:all) do
1169
+ @db = POSTGRES_DB
1170
+ @db.create_table!(:test_cursor){Integer :x}
1171
+ @db.sqls.clear
1172
+ @ds = @db[:test_cursor]
1173
+ @db.transaction{1001.times{|i| @ds.insert(i)}}
1174
+ end
1175
+ after(:all) do
1176
+ @db.drop_table?(:test_cursor)
1177
+ end
1178
+
988
1179
  specify "should return the same results as the non-cursor use" do
989
1180
  @ds.all.should == @ds.use_cursor.all
990
1181
  end
991
-
1182
+
992
1183
  specify "should respect the :rows_per_fetch option" do
993
1184
  @db.sqls.clear
994
1185
  @ds.use_cursor.all
995
- @db.sqls.length.should == 6
996
- @db.sqls.clear
1186
+ check_sqls do
1187
+ @db.sqls.length.should == 6
1188
+ @db.sqls.clear
1189
+ end
997
1190
  @ds.use_cursor(:rows_per_fetch=>100).all
998
- @db.sqls.length.should == 15
1191
+ check_sqls do
1192
+ @db.sqls.length.should == 15
1193
+ end
999
1194
  end
1000
-
1195
+
1001
1196
  specify "should handle returning inside block" do
1002
1197
  def @ds.check_return
1003
1198
  use_cursor.each{|r| return}
@@ -1005,5 +1200,1572 @@ describe "Postgres::Dataset #use_cursor" do
1005
1200
  @ds.check_return
1006
1201
  @ds.all.should == @ds.use_cursor.all
1007
1202
  end
1203
+ end
1204
+
1205
+ describe "Postgres::PG_NAMED_TYPES" do
1206
+ before do
1207
+ @db = POSTGRES_DB
1208
+ Sequel::Postgres::PG_NAMED_TYPES[:interval] = lambda{|v| v.reverse}
1209
+ @db.reset_conversion_procs
1210
+ end
1211
+ after do
1212
+ Sequel::Postgres::PG_NAMED_TYPES.delete(:interval)
1213
+ @db.reset_conversion_procs
1214
+ @db.drop_table?(:foo)
1215
+ end
1216
+
1217
+ specify "should look up conversion procs by name" do
1218
+ @db.create_table!(:foo){interval :bar}
1219
+ @db[:foo].insert(Sequel.cast('21 days', :interval))
1220
+ @db[:foo].get(:bar).should == 'syad 12'
1221
+ end
1222
+ end
1008
1223
  end
1224
+
1225
+ if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && POSTGRES_DB.server_version >= 90000
1226
+ describe "Postgres::Database#copy_table" do
1227
+ before(:all) do
1228
+ @db = POSTGRES_DB
1229
+ @db.create_table!(:test_copy){Integer :x; Integer :y}
1230
+ ds = @db[:test_copy]
1231
+ ds.insert(1, 2)
1232
+ ds.insert(3, 4)
1233
+ end
1234
+ after(:all) do
1235
+ @db.drop_table?(:test_copy)
1236
+ end
1237
+
1238
+ specify "without a block or options should return a text version of the table as a single string" do
1239
+ @db.copy_table(:test_copy).should == "1\t2\n3\t4\n"
1240
+ end
1241
+
1242
+ specify "without a block and with :format=>:csv should return a csv version of the table as a single string" do
1243
+ @db.copy_table(:test_copy, :format=>:csv).should == "1,2\n3,4\n"
1244
+ end
1245
+
1246
+ specify "should treat string as SQL code" do
1247
+ @db.copy_table('COPY "test_copy" TO STDOUT').should == "1\t2\n3\t4\n"
1248
+ end
1249
+
1250
+ specify "should respect given :options options" do
1251
+ @db.copy_table(:test_copy, :options=>"FORMAT csv, HEADER TRUE").should == "x,y\n1,2\n3,4\n"
1252
+ end
1253
+
1254
+ specify "should respect given :options options when :format is used" do
1255
+ @db.copy_table(:test_copy, :format=>:csv, :options=>"QUOTE '''', FORCE_QUOTE *").should == "'1','2'\n'3','4'\n"
1256
+ end
1257
+
1258
+ specify "should accept dataset as first argument" do
1259
+ @db.copy_table(@db[:test_copy].cross_join(:test_copy___tc).order(:test_copy__x, :test_copy__y, :tc__x, :tc__y)).should == "1\t2\t1\t2\n1\t2\t3\t4\n3\t4\t1\t2\n3\t4\t3\t4\n"
1260
+ end
1261
+
1262
+ specify "with a block and no options should yield each row as a string in text format" do
1263
+ buf = []
1264
+ @db.copy_table(:test_copy){|b| buf << b}
1265
+ buf.should == ["1\t2\n", "3\t4\n"]
1266
+ end
1267
+
1268
+ specify "with a block and :format=>:csv should yield each row as a string in csv format" do
1269
+ buf = []
1270
+ @db.copy_table(:test_copy, :format=>:csv){|b| buf << b}
1271
+ buf.should == ["1,2\n", "3,4\n"]
1272
+ end
1273
+
1274
+ specify "should work fine when using a block that is terminated early with a following copy_table" do
1275
+ buf = []
1276
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; break}}.should raise_error(Sequel::DatabaseDisconnectError)
1277
+ buf.should == ["1,2\n"]
1278
+ buf.clear
1279
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; raise ArgumentError}}.should raise_error(Sequel::DatabaseDisconnectError)
1280
+ buf.should == ["1,2\n"]
1281
+ buf.clear
1282
+ @db.copy_table(:test_copy){|b| buf << b}
1283
+ buf.should == ["1\t2\n", "3\t4\n"]
1284
+ end
1285
+
1286
+ specify "should work fine when using a block that is terminated early with a following regular query" do
1287
+ buf = []
1288
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; break}}.should raise_error(Sequel::DatabaseDisconnectError)
1289
+ buf.should == ["1,2\n"]
1290
+ buf.clear
1291
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; raise ArgumentError}}.should raise_error(Sequel::DatabaseDisconnectError)
1292
+ buf.should == ["1,2\n"]
1293
+ @db[:test_copy].select_order_map(:x).should == [1, 3]
1294
+ end
1295
+ end
1296
+
1297
+ describe "Postgres::Database#copy_table_from" do
1298
+ before(:all) do
1299
+ @db = POSTGRES_DB
1300
+ @db.create_table!(:test_copy){Integer :x; Integer :y}
1301
+ @ds = @db[:test_copy].order(:x, :y)
1302
+ end
1303
+ before do
1304
+ @db[:test_copy].delete
1305
+ end
1306
+ after(:all) do
1307
+ @db.drop_table?(:test_copy)
1308
+ end
1309
+
1310
+ specify "should work with a :data option containing data in PostgreSQL text format" do
1311
+ @db.copy_into(:test_copy, :data=>"1\t2\n3\t4\n")
1312
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1313
+ end
1314
+
1315
+ specify "should work with :format=>:csv option and :data option containing data in CSV format" do
1316
+ @db.copy_into(:test_copy, :format=>:csv, :data=>"1,2\n3,4\n")
1317
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1318
+ end
1319
+
1320
+ specify "should respect given :options" do
1321
+ @db.copy_into(:test_copy, :options=>"FORMAT csv, HEADER TRUE", :data=>"x,y\n1,2\n3,4\n")
1322
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1323
+ end
1324
+
1325
+ specify "should respect given :options options when :format is used" do
1326
+ @db.copy_into(:test_copy, :options=>"QUOTE '''', DELIMITER '|'", :format=>:csv, :data=>"'1'|'2'\n'3'|'4'\n")
1327
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1328
+ end
1329
+
1330
+ specify "should accept :columns option to online copy the given columns" do
1331
+ @db.copy_into(:test_copy, :data=>"1\t2\n3\t4\n", :columns=>[:y, :x])
1332
+ @ds.select_map([:x, :y]).should == [[2, 1], [4, 3]]
1333
+ end
1334
+
1335
+ specify "should accept a block and use returned values for the copy in data stream" do
1336
+ buf = ["1\t2\n", "3\t4\n"]
1337
+ @db.copy_into(:test_copy){buf.shift}
1338
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1339
+ end
1340
+
1341
+ specify "should work correctly with a block and :format=>:csv" do
1342
+ buf = ["1,2\n", "3,4\n"]
1343
+ @db.copy_into(:test_copy, :format=>:csv){buf.shift}
1344
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1345
+ end
1346
+
1347
+ specify "should accept an enumerable as the :data option" do
1348
+ @db.copy_into(:test_copy, :data=>["1\t2\n", "3\t4\n"])
1349
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1350
+ end
1351
+
1352
+ specify "should have an exception should cause a rollback of copied data and still have a usable connection" do
1353
+ 2.times do
1354
+ sent = false
1355
+ proc{@db.copy_into(:test_copy){raise ArgumentError if sent; sent = true; "1\t2\n"}}.should raise_error(ArgumentError)
1356
+ @ds.select_map([:x, :y]).should == []
1357
+ end
1358
+ end
1359
+
1360
+ specify "should raise an Error if both :data and a block are provided" do
1361
+ proc{@db.copy_into(:test_copy, :data=>["1\t2\n", "3\t4\n"]){}}.should raise_error(Sequel::Error)
1362
+ end
1363
+
1364
+ specify "should raise an Error if neither :data or a block are provided" do
1365
+ proc{@db.copy_into(:test_copy)}.should raise_error(Sequel::Error)
1366
+ end
1367
+ end
1368
+
1369
+ describe "Postgres::Database LISTEN/NOTIFY" do
1370
+ before(:all) do
1371
+ @db = POSTGRES_DB
1372
+ end
1373
+
1374
+ specify "should support listen and notify" do
1375
+ notify_pid = @db.synchronize{|conn| conn.backend_pid}
1376
+
1377
+ called = false
1378
+ @db.listen('foo', :after_listen=>proc{@db.notify('foo')}) do |ev, pid, payload|
1379
+ ev.should == 'foo'
1380
+ pid.should == notify_pid
1381
+ ['', nil].should include(payload)
1382
+ called = true
1383
+ end.should == 'foo'
1384
+ called.should be_true
1385
+
1386
+ # Check weird identifier names
1387
+ called = false
1388
+ @db.listen('FOO bar', :after_listen=>proc{@db.notify('FOO bar')}) do |ev, pid, payload|
1389
+ ev.should == 'FOO bar'
1390
+ pid.should == notify_pid
1391
+ ['', nil].should include(payload)
1392
+ called = true
1393
+ end.should == 'FOO bar'
1394
+ called.should be_true
1395
+
1396
+ # Check identifier symbols
1397
+ called = false
1398
+ @db.listen(:foo, :after_listen=>proc{@db.notify(:foo)}) do |ev, pid, payload|
1399
+ ev.should == 'foo'
1400
+ pid.should == notify_pid
1401
+ ['', nil].should include(payload)
1402
+ called = true
1403
+ end.should == 'foo'
1404
+ called.should be_true
1405
+
1406
+ called = false
1407
+ @db.listen('foo', :after_listen=>proc{@db.notify('foo', :payload=>'bar')}) do |ev, pid, payload|
1408
+ ev.should == 'foo'
1409
+ pid.should == notify_pid
1410
+ payload.should == 'bar'
1411
+ called = true
1412
+ end.should == 'foo'
1413
+ called.should be_true
1414
+
1415
+ @db.listen('foo', :after_listen=>proc{@db.notify('foo')}).should == 'foo'
1416
+
1417
+ called = false
1418
+ called2 = false
1419
+ i = 0
1420
+ @db.listen(['foo', 'bar'], :after_listen=>proc{@db.notify('foo', :payload=>'bar'); @db.notify('bar', :payload=>'foo')}, :loop=>proc{i+=1}) do |ev, pid, payload|
1421
+ if !called
1422
+ ev.should == 'foo'
1423
+ pid.should == notify_pid
1424
+ payload.should == 'bar'
1425
+ called = true
1426
+ else
1427
+ ev.should == 'bar'
1428
+ pid.should == notify_pid
1429
+ payload.should == 'foo'
1430
+ called2 = true
1431
+ break
1432
+ end
1433
+ end.should be_nil
1434
+ called.should be_true
1435
+ called2.should be_true
1436
+ i.should == 1
1437
+ end
1438
+
1439
+ specify "should accept a :timeout option in listen" do
1440
+ @db.listen('foo2', :timeout=>0.001).should == nil
1441
+ called = false
1442
+ @db.listen('foo2', :timeout=>0.001){|ev, pid, payload| called = true}.should == nil
1443
+ called.should be_false
1444
+ i = 0
1445
+ @db.listen('foo2', :timeout=>0.001, :loop=>proc{i+=1; throw :stop if i > 3}){|ev, pid, payload| called = true}.should == nil
1446
+ i.should == 4
1447
+ end unless RUBY_PLATFORM =~ /mingw/ # Ruby freezes on this spec on this platform/version
1448
+ end
1449
+ end
1450
+
1451
+ describe 'PostgreSQL special float handling' do
1452
+ before do
1453
+ @db = POSTGRES_DB
1454
+ @db.create_table!(:test5){Float :value}
1455
+ @db.sqls.clear
1456
+ @ds = @db[:test5]
1457
+ end
1458
+ after do
1459
+ @db.drop_table?(:test5)
1460
+ end
1461
+
1462
+ check_sqls do
1463
+ specify 'should quote NaN' do
1464
+ nan = 0.0/0.0
1465
+ @ds.insert_sql(:value => nan).should == %q{INSERT INTO "test5" ("value") VALUES ('NaN')}
1466
+ end
1467
+
1468
+ specify 'should quote +Infinity' do
1469
+ inf = 1.0/0.0
1470
+ @ds.insert_sql(:value => inf).should == %q{INSERT INTO "test5" ("value") VALUES ('Infinity')}
1471
+ end
1472
+
1473
+ specify 'should quote -Infinity' do
1474
+ inf = -1.0/0.0
1475
+ @ds.insert_sql(:value => inf).should == %q{INSERT INTO "test5" ("value") VALUES ('-Infinity')}
1476
+ end
1477
+ end
1478
+
1479
+ if POSTGRES_DB.adapter_scheme == :postgres
1480
+ specify 'inserts NaN' do
1481
+ nan = 0.0/0.0
1482
+ @ds.insert(:value=>nan)
1483
+ @ds.all[0][:value].nan?.should be_true
1484
+ end
1485
+
1486
+ specify 'inserts +Infinity' do
1487
+ inf = 1.0/0.0
1488
+ @ds.insert(:value=>inf)
1489
+ @ds.all[0][:value].infinite?.should > 0
1490
+ end
1491
+
1492
+ specify 'inserts -Infinity' do
1493
+ inf = -1.0/0.0
1494
+ @ds.insert(:value=>inf)
1495
+ @ds.all[0][:value].infinite?.should < 0
1496
+ end
1497
+ end
1498
+ end
1499
+
1500
+ describe 'PostgreSQL array handling' do
1501
+ before(:all) do
1502
+ @db = POSTGRES_DB
1503
+ @db.extension :pg_array
1504
+ @ds = @db[:items]
1505
+ @native = POSTGRES_DB.adapter_scheme == :postgres
1506
+ @jdbc = POSTGRES_DB.adapter_scheme == :jdbc
1507
+ @tp = lambda{@db.schema(:items).map{|a| a.last[:type]}}
1508
+ end
1509
+ after do
1510
+ @db.drop_table?(:items)
1511
+ end
1512
+
1513
+ specify 'insert and retrieve integer and float arrays of various sizes' do
1514
+ @db.create_table!(:items) do
1515
+ column :i2, 'int2[]'
1516
+ column :i4, 'int4[]'
1517
+ column :i8, 'int8[]'
1518
+ column :r, 'real[]'
1519
+ column :dp, 'double precision[]'
1520
+ end
1521
+ @tp.call.should == [:integer_array, :integer_array, :bigint_array, :float_array, :float_array]
1522
+ @ds.insert(Sequel.pg_array([1], :int2), Sequel.pg_array([nil, 2], :int4), Sequel.pg_array([3, nil], :int8), Sequel.pg_array([4, nil, 4.5], :real), Sequel.pg_array([5, nil, 5.5], "double precision"))
1523
+ @ds.count.should == 1
1524
+ rs = @ds.all
1525
+ if @jdbc || @native
1526
+ rs.should == [{:i2=>[1], :i4=>[nil, 2], :i8=>[3, nil], :r=>[4.0, nil, 4.5], :dp=>[5.0, nil, 5.5]}]
1527
+ end
1528
+ if @native
1529
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1530
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1531
+ @ds.delete
1532
+ @ds.insert(rs.first)
1533
+ @ds.all.should == rs
1534
+ end
1535
+
1536
+ @ds.delete
1537
+ @ds.insert(Sequel.pg_array([[1], [2]], :int2), Sequel.pg_array([[nil, 2], [3, 4]], :int4), Sequel.pg_array([[3, nil], [nil, nil]], :int8), Sequel.pg_array([[4, nil], [nil, 4.5]], :real), Sequel.pg_array([[5, nil], [nil, 5.5]], "double precision"))
1538
+
1539
+ rs = @ds.all
1540
+ if @jdbc || @native
1541
+ rs.should == [{:i2=>[[1], [2]], :i4=>[[nil, 2], [3, 4]], :i8=>[[3, nil], [nil, nil]], :r=>[[4, nil], [nil, 4.5]], :dp=>[[5, nil], [nil, 5.5]]}]
1542
+ end
1543
+ if @native
1544
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1545
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1546
+ @ds.delete
1547
+ @ds.insert(rs.first)
1548
+ @ds.all.should == rs
1549
+ end
1550
+ end
1551
+
1552
+ specify 'insert and retrieve decimal arrays' do
1553
+ @db.create_table!(:items) do
1554
+ column :n, 'numeric[]'
1555
+ end
1556
+ @tp.call.should == [:decimal_array]
1557
+ @ds.insert(Sequel.pg_array([BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')], :numeric))
1558
+ @ds.count.should == 1
1559
+ rs = @ds.all
1560
+ if @jdbc || @native
1561
+ rs.should == [{:n=>[BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')]}]
1562
+ end
1563
+ if @native
1564
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1565
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1566
+ @ds.delete
1567
+ @ds.insert(rs.first)
1568
+ @ds.all.should == rs
1569
+ end
1570
+
1571
+ @ds.delete
1572
+ @ds.insert(Sequel.pg_array([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]], :numeric))
1573
+ rs = @ds.all
1574
+ if @jdbc || @native
1575
+ rs.should == [{:n=>[[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]]}]
1576
+ end
1577
+ if @native
1578
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1579
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1580
+ @ds.delete
1581
+ @ds.insert(rs.first)
1582
+ @ds.all.should == rs
1583
+ end
1584
+ end
1585
+
1586
+ specify 'insert and retrieve string arrays' do
1587
+ @db.create_table!(:items) do
1588
+ column :c, 'char(4)[]'
1589
+ column :vc, 'varchar[]'
1590
+ column :t, 'text[]'
1591
+ end
1592
+ @tp.call.should == [:string_array, :string_array, :string_array]
1593
+ @ds.insert(Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], 'char(4)'), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], :varchar), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], :text))
1594
+ @ds.count.should == 1
1595
+ rs = @ds.all
1596
+ if @jdbc || @native
1597
+ rs.should == [{:c=>['a ', nil, 'NULL', 'b"\'c'], :vc=>['a', nil, 'NULL', 'b"\'c'], :t=>['a', nil, 'NULL', 'b"\'c']}]
1598
+ end
1599
+ if @native
1600
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1601
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1602
+ @ds.delete
1603
+ @ds.insert(rs.first)
1604
+ @ds.all.should == rs
1605
+ end
1606
+
1607
+ @ds.delete
1608
+ @ds.insert(Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], 'char(4)'), Sequel.pg_array([[['a'], ['']], [['NULL'], ['b"\'c']]], :varchar), Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], :text))
1609
+ rs = @ds.all
1610
+ if @jdbc || @native
1611
+ rs.should == [{:c=>[[['a '], [nil]], [['NULL'], ['b"\'c']]], :vc=>[[['a'], ['']], [['NULL'], ['b"\'c']]], :t=>[[['a'], [nil]], [['NULL'], ['b"\'c']]]}]
1612
+ end
1613
+ if @native
1614
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1615
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1616
+ @ds.delete
1617
+ @ds.insert(rs.first)
1618
+ @ds.all.should == rs
1619
+ end
1620
+ end
1621
+
1622
+ specify 'insert and retrieve arrays of other types' do
1623
+ @db.create_table!(:items) do
1624
+ column :b, 'bool[]'
1625
+ column :d, 'date[]'
1626
+ column :t, 'time[]'
1627
+ column :ts, 'timestamp[]'
1628
+ column :tstz, 'timestamptz[]'
1629
+ end
1630
+ @tp.call.should == [:boolean_array, :date_array, :time_array, :datetime_array, :datetime_timezone_array]
1631
+
1632
+ d = Date.today
1633
+ t = Sequel::SQLTime.create(10, 20, 30)
1634
+ ts = Time.local(2011, 1, 2, 3, 4, 5)
1635
+
1636
+ @ds.insert(Sequel.pg_array([true, false], :bool), Sequel.pg_array([d, nil], :date), Sequel.pg_array([t, nil], :time), Sequel.pg_array([ts, nil], :timestamp), Sequel.pg_array([ts, nil], :timestamptz))
1637
+ @ds.count.should == 1
1638
+ rs = @ds.all
1639
+ if @jdbc || @native
1640
+ rs.should == [{:b=>[true, false], :d=>[d, nil], :t=>[t, nil], :ts=>[ts, nil], :tstz=>[ts, nil]}]
1641
+ end
1642
+ if @native
1643
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1644
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1645
+ @ds.delete
1646
+ @ds.insert(rs.first)
1647
+ @ds.all.should == rs
1648
+ end
1649
+
1650
+ @db.create_table!(:items) do
1651
+ column :ba, 'bytea[]'
1652
+ column :tz, 'timetz[]'
1653
+ column :o, 'oid[]'
1654
+ end
1655
+ @tp.call.should == [:blob_array, :time_timezone_array, :integer_array]
1656
+ @ds.insert(Sequel.pg_array([Sequel.blob("a\0"), nil], :bytea), Sequel.pg_array([t, nil], :timetz), Sequel.pg_array([1, 2, 3], :oid))
1657
+ @ds.count.should == 1
1658
+ if @native
1659
+ rs = @ds.all
1660
+ rs.should == [{:ba=>[Sequel.blob("a\0"), nil], :tz=>[t, nil], :o=>[1, 2, 3]}]
1661
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1662
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1663
+ @ds.delete
1664
+ @ds.insert(rs.first)
1665
+ @ds.all.should == rs
1666
+ end
1667
+ end
1668
+
1669
+ specify 'use arrays in bound variables' do
1670
+ @db.create_table!(:items) do
1671
+ column :i, 'int4[]'
1672
+ end
1673
+ @ds.call(:insert, {:i=>[1,2]}, {:i=>:$i})
1674
+ @ds.get(:i).should == [1, 2]
1675
+ @ds.filter(:i=>:$i).call(:first, :i=>[1,2]).should == {:i=>[1,2]}
1676
+ @ds.filter(:i=>:$i).call(:first, :i=>[1,3]).should == nil
1677
+
1678
+ # NULL values
1679
+ @ds.delete
1680
+ @ds.call(:insert, {:i=>[nil,nil]}, {:i=>:$i})
1681
+ @ds.first.should == {:i=>[nil, nil]}
1682
+
1683
+ @db.create_table!(:items) do
1684
+ column :i, 'text[]'
1685
+ end
1686
+ a = ["\"\\\\\"{}\n\t\r \v\b123afP", 'NULL', nil, '']
1687
+ @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a))
1688
+ @ds.get(:i).should == a
1689
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1690
+ @ds.filter(:i=>:$i).call(:first, :i=>['', nil, nil, 'a']).should == nil
1691
+
1692
+ @db.create_table!(:items) do
1693
+ column :i, 'date[]'
1694
+ end
1695
+ a = [Date.today]
1696
+ @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'date'))
1697
+ @ds.get(:i).should == a
1698
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1699
+ @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([Date.today-1], 'date')).should == nil
1700
+
1701
+ @db.create_table!(:items) do
1702
+ column :i, 'timestamp[]'
1703
+ end
1704
+ a = [Time.local(2011, 1, 2, 3, 4, 5)]
1705
+ @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'timestamp'))
1706
+ @ds.get(:i).should == a
1707
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1708
+ @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([a.first-1], 'timestamp')).should == nil
1709
+
1710
+ @db.create_table!(:items) do
1711
+ column :i, 'boolean[]'
1712
+ end
1713
+ a = [true, false]
1714
+ @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'boolean'))
1715
+ @ds.get(:i).should == a
1716
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1717
+ @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([false, true], 'boolean')).should == nil
1718
+
1719
+ @db.create_table!(:items) do
1720
+ column :i, 'bytea[]'
1721
+ end
1722
+ a = [Sequel.blob("a\0'\"")]
1723
+ @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'bytea'))
1724
+ @ds.get(:i).should == a
1725
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1726
+ @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([Sequel.blob("b\0")], 'bytea')).should == nil
1727
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
1728
+
1729
+ specify 'with models' do
1730
+ @db.create_table!(:items) do
1731
+ primary_key :id
1732
+ column :i, 'integer[]'
1733
+ column :f, 'double precision[]'
1734
+ column :d, 'numeric[]'
1735
+ column :t, 'text[]'
1736
+ end
1737
+ c = Class.new(Sequel::Model(@db[:items]))
1738
+ c.plugin :pg_typecast_on_load, :i, :f, :d, :t unless @native
1739
+ o = c.create(:i=>[1,2, nil], :f=>[[1, 2.5], [3, 4.5]], :d=>[1, BigDecimal.new('1.000000000000000000001')], :t=>[%w'a b c', ['NULL', nil, '1']])
1740
+ o.i.should == [1, 2, nil]
1741
+ o.f.should == [[1, 2.5], [3, 4.5]]
1742
+ o.d.should == [BigDecimal.new('1'), BigDecimal.new('1.000000000000000000001')]
1743
+ o.t.should == [%w'a b c', ['NULL', nil, '1']]
1744
+ end
1745
+
1746
+ specify 'operations/functions with pg_array_ops' do
1747
+ Sequel.extension :pg_array_ops
1748
+ @db.create_table!(:items){column :i, 'integer[]'; column :i2, 'integer[]'; column :i3, 'integer[]'; column :i4, 'integer[]'; column :i5, 'integer[]'}
1749
+ @ds.insert(Sequel.pg_array([1, 2, 3]), Sequel.pg_array([2, 1]), Sequel.pg_array([4, 4]), Sequel.pg_array([[5, 5], [4, 3]]), Sequel.pg_array([1, nil, 5]))
1750
+
1751
+ @ds.get(Sequel.pg_array(:i) > :i3).should be_false
1752
+ @ds.get(Sequel.pg_array(:i3) > :i).should be_true
1753
+
1754
+ @ds.get(Sequel.pg_array(:i) >= :i3).should be_false
1755
+ @ds.get(Sequel.pg_array(:i) >= :i).should be_true
1756
+
1757
+ @ds.get(Sequel.pg_array(:i3) < :i).should be_false
1758
+ @ds.get(Sequel.pg_array(:i) < :i3).should be_true
1759
+
1760
+ @ds.get(Sequel.pg_array(:i3) <= :i).should be_false
1761
+ @ds.get(Sequel.pg_array(:i) <= :i).should be_true
1762
+
1763
+ @ds.get(Sequel.expr(5=>Sequel.pg_array(:i).any)).should be_false
1764
+ @ds.get(Sequel.expr(1=>Sequel.pg_array(:i).any)).should be_true
1765
+
1766
+ @ds.get(Sequel.expr(1=>Sequel.pg_array(:i3).all)).should be_false
1767
+ @ds.get(Sequel.expr(4=>Sequel.pg_array(:i3).all)).should be_true
1768
+
1769
+ @ds.get(Sequel.pg_array(:i2)[1]).should == 2
1770
+ @ds.get(Sequel.pg_array(:i2)[2]).should == 1
1771
+
1772
+ @ds.get(Sequel.pg_array(:i4)[2][1]).should == 4
1773
+ @ds.get(Sequel.pg_array(:i4)[2][2]).should == 3
1774
+
1775
+ @ds.get(Sequel.pg_array(:i).contains(:i2)).should be_true
1776
+ @ds.get(Sequel.pg_array(:i).contains(:i3)).should be_false
1777
+
1778
+ @ds.get(Sequel.pg_array(:i2).contained_by(:i)).should be_true
1779
+ @ds.get(Sequel.pg_array(:i).contained_by(:i2)).should be_false
1780
+
1781
+ @ds.get(Sequel.pg_array(:i).overlaps(:i2)).should be_true
1782
+ @ds.get(Sequel.pg_array(:i2).overlaps(:i3)).should be_false
1783
+
1784
+ @ds.get(Sequel.pg_array(:i).dims).should == '[1:3]'
1785
+ @ds.get(Sequel.pg_array(:i).length).should == 3
1786
+ @ds.get(Sequel.pg_array(:i).lower).should == 1
1787
+
1788
+ if @db.server_version >= 90000
1789
+ @ds.get(Sequel.pg_array(:i5).join).should == '15'
1790
+ @ds.get(Sequel.pg_array(:i5).join(':')).should == '1:5'
1791
+ @ds.get(Sequel.pg_array(:i5).join(':', '*')).should == '1:*:5'
1792
+ end
1793
+ @ds.select(Sequel.pg_array(:i).unnest).from_self.count.should == 3 if @db.server_version >= 80400
1794
+
1795
+ if @native
1796
+ @ds.get(Sequel.pg_array(:i).push(4)).should == [1, 2, 3, 4]
1797
+ @ds.get(Sequel.pg_array(:i).unshift(4)).should == [4, 1, 2, 3]
1798
+ @ds.get(Sequel.pg_array(:i).concat(:i2)).should == [1, 2, 3, 2, 1]
1799
+ end
1800
+ end
1801
+ end
1802
+
1803
+ describe 'PostgreSQL hstore handling' do
1804
+ before(:all) do
1805
+ @db = POSTGRES_DB
1806
+ @db.extension :pg_hstore
1807
+ @ds = @db[:items]
1808
+ @h = {'a'=>'b', 'c'=>nil, 'd'=>'NULL', 'e'=>'\\\\" \\\' ,=>'}
1809
+ @native = POSTGRES_DB.adapter_scheme == :postgres
1810
+ end
1811
+ after do
1812
+ @db.drop_table?(:items)
1813
+ end
1814
+
1815
+ specify 'insert and retrieve hstore values' do
1816
+ @db.create_table!(:items) do
1817
+ column :h, :hstore
1818
+ end
1819
+ @ds.insert(Sequel.hstore(@h))
1820
+ @ds.count.should == 1
1821
+ if @native
1822
+ rs = @ds.all
1823
+ v = rs.first[:h]
1824
+ v.should_not be_a_kind_of(Hash)
1825
+ v.to_hash.should be_a_kind_of(Hash)
1826
+ v.to_hash.should == @h
1827
+ @ds.delete
1828
+ @ds.insert(rs.first)
1829
+ @ds.all.should == rs
1830
+ end
1831
+ end
1832
+
1833
+ specify 'use hstore in bound variables' do
1834
+ @db.create_table!(:items) do
1835
+ column :i, :hstore
1836
+ end
1837
+ @ds.call(:insert, {:i=>Sequel.hstore(@h)}, {:i=>:$i})
1838
+ @ds.get(:i).should == @h
1839
+ @ds.filter(:i=>:$i).call(:first, :i=>Sequel.hstore(@h)).should == {:i=>@h}
1840
+ @ds.filter(:i=>:$i).call(:first, :i=>Sequel.hstore({})).should == nil
1841
+
1842
+ @ds.delete
1843
+ @ds.call(:insert, {:i=>Sequel.hstore('a'=>nil)}, {:i=>:$i})
1844
+ @ds.get(:i).should == Sequel.hstore('a'=>nil)
1845
+
1846
+ @ds.delete
1847
+ @ds.call(:insert, {:i=>@h}, {:i=>:$i})
1848
+ @ds.get(:i).should == @h
1849
+ @ds.filter(:i=>:$i).call(:first, :i=>@h).should == {:i=>@h}
1850
+ @ds.filter(:i=>:$i).call(:first, :i=>{}).should == nil
1851
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
1852
+
1853
+ specify 'with models and associations' do
1854
+ @db.create_table!(:items) do
1855
+ primary_key :id
1856
+ column :h, :hstore
1857
+ end
1858
+ c = Class.new(Sequel::Model(@db[:items])) do
1859
+ def self.name
1860
+ 'Item'
1861
+ end
1862
+ unrestrict_primary_key
1863
+ def item_id
1864
+ h['item_id'].to_i if h
1865
+ end
1866
+ def left_item_id
1867
+ h['left_item_id'].to_i if h
1868
+ end
1869
+ end
1870
+ Sequel.extension :pg_hstore_ops
1871
+ c.plugin :many_through_many
1872
+ c.plugin :pg_typecast_on_load, :h unless @native
1873
+
1874
+ h = {'item_id'=>"2", 'left_item_id'=>"1"}
1875
+ o2 = c.create(:id=>2)
1876
+ o = c.create(:id=>1, :h=>h)
1877
+ o.h.should == h
1878
+
1879
+ c.many_to_one :item, :class=>c, :key_column=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
1880
+ c.one_to_many :items, :class=>c, :key=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer), :key_method=>:item_id
1881
+ c.many_to_many :related_items, :class=>c, :join_table=>:items___i, :left_key=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer), :right_key=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
1882
+
1883
+ c.many_to_one :other_item, :class=>c, :key=>:id, :primary_key_method=>:item_id, :primary_key=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
1884
+ c.one_to_many :other_items, :class=>c, :primary_key=>:item_id, :key=>:id, :primary_key_column=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
1885
+ c.many_to_many :other_related_items, :class=>c, :join_table=>:items___i, :left_key=>:id, :right_key=>:id,
1886
+ :left_primary_key_column=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer),
1887
+ :left_primary_key=>:left_item_id,
1888
+ :right_primary_key=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer),
1889
+ :right_primary_key_method=>:left_item_id
1890
+
1891
+ c.many_through_many :mtm_items, [
1892
+ [:items, Sequel.cast(Sequel.hstore(:h)['item_id'], Integer), Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer)],
1893
+ [:items, Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer), Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer)]
1894
+ ],
1895
+ :class=>c,
1896
+ :left_primary_key_column=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer),
1897
+ :left_primary_key=>:item_id,
1898
+ :right_primary_key=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer),
1899
+ :right_primary_key_method=>:left_item_id
1900
+
1901
+ # Lazily Loading
1902
+ o.item.should == o2
1903
+ o2.items.should == [o]
1904
+ o.related_items.should == [o2]
1905
+ o2.other_item.should == o
1906
+ o.other_items.should == [o2]
1907
+ o.other_related_items.should == [o]
1908
+ o.mtm_items.should == [o]
1909
+
1910
+ # Eager Loading via eager
1911
+ os = c.eager(:item, :related_items, :other_items, :other_related_items, :mtm_items).where(:id=>1).all.first
1912
+ os.item.should == o2
1913
+ os.related_items.should == [o2]
1914
+ os.other_items.should == [o2]
1915
+ os.other_related_items.should == [o]
1916
+ os.mtm_items.should == [o]
1917
+ os = c.eager(:items, :other_item).where(:id=>2).all.first
1918
+ os.items.should == [o]
1919
+ os.other_item.should == o
1920
+
1921
+ # Eager Loading via eager_graph
1922
+ c.eager_graph(:item).where(:items__id=>1).all.first.item.should == o2
1923
+ c.eager_graph(:items).where(:items__id=>2).all.first.items.should == [o]
1924
+ c.eager_graph(:related_items).where(:items__id=>1).all.first.related_items.should == [o2]
1925
+ c.eager_graph(:other_item).where(:items__id=>2).all.first.other_item.should == o
1926
+ c.eager_graph(:other_items).where(:items__id=>1).all.first.other_items.should == [o2]
1927
+ c.eager_graph(:other_related_items).where(:items__id=>1).all.first.other_related_items.should == [o]
1928
+ c.eager_graph(:mtm_items).where(:items__id=>1).all.first.mtm_items.should == [o]
1929
+
1930
+ # Filter By Associations - Model Instances
1931
+ c.filter(:item=>o2).all.should == [o]
1932
+ c.filter(:items=>o).all.should == [o2]
1933
+ c.filter(:related_items=>o2).all.should == [o]
1934
+ c.filter(:other_item=>o).all.should == [o2]
1935
+ c.filter(:other_items=>o2).all.should == [o]
1936
+ c.filter(:other_related_items=>o).all.should == [o]
1937
+ c.filter(:mtm_items=>o).all.should == [o]
1938
+
1939
+ # Filter By Associations - Model Datasets
1940
+ c.filter(:item=>c.filter(:id=>o2.id)).all.should == [o]
1941
+ c.filter(:items=>c.filter(:id=>o.id)).all.should == [o2]
1942
+ c.filter(:related_items=>c.filter(:id=>o2.id)).all.should == [o]
1943
+ c.filter(:other_item=>c.filter(:id=>o.id)).all.should == [o2]
1944
+ c.filter(:other_items=>c.filter(:id=>o2.id)).all.should == [o]
1945
+ c.filter(:other_related_items=>c.filter(:id=>o.id)).all.should == [o]
1946
+ c.filter(:mtm_items=>c.filter(:id=>o.id)).all.should == [o]
1947
+ end
1948
+
1949
+ specify 'operations/functions with pg_hstore_ops' do
1950
+ Sequel.extension :pg_hstore_ops, :pg_array, :pg_array_ops
1951
+ @db.create_table!(:items){hstore :h1; hstore :h2; hstore :h3; String :t}
1952
+ @ds.insert(Sequel.hstore('a'=>'b', 'c'=>nil), Sequel.hstore('a'=>'b'), Sequel.hstore('d'=>'e'))
1953
+ h1 = Sequel.hstore(:h1)
1954
+ h2 = Sequel.hstore(:h2)
1955
+ h3 = Sequel.hstore(:h3)
1956
+
1957
+ @ds.get(h1['a']).should == 'b'
1958
+ @ds.get(h1['d']).should == nil
1959
+
1960
+ @ds.get(h2.concat(h3).keys.pg_array.length).should == 2
1961
+ @ds.get(h1.concat(h3).keys.pg_array.length).should == 3
1962
+ @ds.get(h2.merge(h3).keys.pg_array.length).should == 2
1963
+ @ds.get(h1.merge(h3).keys.pg_array.length).should == 3
1964
+
1965
+ unless [:do].include?(@db.adapter_scheme)
1966
+ # Broken DataObjects thinks operators with ? represent placeholders
1967
+ @ds.get(h1.contain_all(Sequel.pg_array(%w'a c'))).should == true
1968
+ @ds.get(h1.contain_all(Sequel.pg_array(%w'a d'))).should == false
1969
+
1970
+ @ds.get(h1.contain_any(Sequel.pg_array(%w'a d'))).should == true
1971
+ @ds.get(h1.contain_any(Sequel.pg_array(%w'e d'))).should == false
1972
+ end
1973
+
1974
+ @ds.get(h1.contains(h2)).should == true
1975
+ @ds.get(h1.contains(h3)).should == false
1976
+
1977
+ @ds.get(h2.contained_by(h1)).should == true
1978
+ @ds.get(h2.contained_by(h3)).should == false
1979
+
1980
+ @ds.get(h1.defined('a')).should == true
1981
+ @ds.get(h1.defined('c')).should == false
1982
+ @ds.get(h1.defined('d')).should == false
1983
+
1984
+ @ds.get(h1.delete('a')['c']).should == nil
1985
+ @ds.get(h1.delete(Sequel.pg_array(%w'a d'))['c']).should == nil
1986
+ @ds.get(h1.delete(h2)['c']).should == nil
1987
+
1988
+ @ds.from(Sequel.hstore('a'=>'b', 'c'=>nil).op.each).order(:key).all.should == [{:key=>'a', :value=>'b'}, {:key=>'c', :value=>nil}]
1989
+
1990
+ unless [:do].include?(@db.adapter_scheme)
1991
+ @ds.get(h1.has_key?('c')).should == true
1992
+ @ds.get(h1.include?('c')).should == true
1993
+ @ds.get(h1.key?('c')).should == true
1994
+ @ds.get(h1.member?('c')).should == true
1995
+ @ds.get(h1.exist?('c')).should == true
1996
+ @ds.get(h1.has_key?('d')).should == false
1997
+ @ds.get(h1.include?('d')).should == false
1998
+ @ds.get(h1.key?('d')).should == false
1999
+ @ds.get(h1.member?('d')).should == false
2000
+ @ds.get(h1.exist?('d')).should == false
2001
+ end
2002
+
2003
+ @ds.get(h1.hstore.hstore.hstore.keys.pg_array.length).should == 2
2004
+ @ds.get(h1.keys.pg_array.length).should == 2
2005
+ @ds.get(h2.keys.pg_array.length).should == 1
2006
+ @ds.get(h1.akeys.pg_array.length).should == 2
2007
+ @ds.get(h2.akeys.pg_array.length).should == 1
2008
+
2009
+ @ds.from(Sequel.hstore('t'=>'s').op.populate(Sequel::SQL::Cast.new(nil, :items))).select_map(:t).should == ['s']
2010
+ @ds.from(:items___i).select(Sequel.hstore('t'=>'s').op.record_set(:i).as(:r)).from_self(:alias=>:s).select(Sequel.lit('(r).*')).from_self.select_map(:t).should == ['s']
2011
+
2012
+ @ds.from(Sequel.hstore('t'=>'s', 'a'=>'b').op.skeys.as(:s)).select_order_map(:s).should == %w'a t'
2013
+
2014
+ @ds.get(h1.slice(Sequel.pg_array(%w'a c')).keys.pg_array.length).should == 2
2015
+ @ds.get(h1.slice(Sequel.pg_array(%w'd c')).keys.pg_array.length).should == 1
2016
+ @ds.get(h1.slice(Sequel.pg_array(%w'd e')).keys.pg_array.length).should == nil
2017
+
2018
+ @ds.from(Sequel.hstore('t'=>'s', 'a'=>'b').op.svals.as(:s)).select_order_map(:s).should == %w'b s'
2019
+
2020
+ @ds.get(h1.to_array.pg_array.length).should == 4
2021
+ @ds.get(h2.to_array.pg_array.length).should == 2
2022
+
2023
+ @ds.get(h1.to_matrix.pg_array.length).should == 2
2024
+ @ds.get(h2.to_matrix.pg_array.length).should == 1
2025
+
2026
+ @ds.get(h1.values.pg_array.length).should == 2
2027
+ @ds.get(h2.values.pg_array.length).should == 1
2028
+ @ds.get(h1.avals.pg_array.length).should == 2
2029
+ @ds.get(h2.avals.pg_array.length).should == 1
2030
+ end
2031
+ end if POSTGRES_DB.type_supported?(:hstore)
2032
+
2033
+ describe 'PostgreSQL json type' do
2034
+ before(:all) do
2035
+ @db = POSTGRES_DB
2036
+ @db.extension :pg_array, :pg_json
2037
+ @ds = @db[:items]
2038
+ @a = [1, 2, {'a'=>'b'}, 3.0]
2039
+ @h = {'a'=>'b', '1'=>[3, 4, 5]}
2040
+ @native = POSTGRES_DB.adapter_scheme == :postgres
2041
+ end
2042
+ after do
2043
+ @db.drop_table?(:items)
2044
+ end
2045
+
2046
+ specify 'insert and retrieve json values' do
2047
+ @db.create_table!(:items){json :j}
2048
+ @ds.insert(Sequel.pg_json(@h))
2049
+ @ds.count.should == 1
2050
+ if @native
2051
+ rs = @ds.all
2052
+ v = rs.first[:j]
2053
+ v.should_not be_a_kind_of(Hash)
2054
+ v.to_hash.should be_a_kind_of(Hash)
2055
+ v.should == @h
2056
+ v.to_hash.should == @h
2057
+ @ds.delete
2058
+ @ds.insert(rs.first)
2059
+ @ds.all.should == rs
2060
+ end
2061
+
2062
+ @ds.delete
2063
+ @ds.insert(Sequel.pg_json(@a))
2064
+ @ds.count.should == 1
2065
+ if @native
2066
+ rs = @ds.all
2067
+ v = rs.first[:j]
2068
+ v.should_not be_a_kind_of(Array)
2069
+ v.to_a.should be_a_kind_of(Array)
2070
+ v.should == @a
2071
+ v.to_a.should == @a
2072
+ @ds.delete
2073
+ @ds.insert(rs.first)
2074
+ @ds.all.should == rs
2075
+ end
2076
+ end
2077
+
2078
+ specify 'insert and retrieve json[] values' do
2079
+ @db.create_table!(:items){column :j, 'json[]'}
2080
+ j = Sequel.pg_array([Sequel.pg_json('a'=>1), Sequel.pg_json(['b', 2])])
2081
+ @ds.insert(j)
2082
+ @ds.count.should == 1
2083
+ if @native
2084
+ rs = @ds.all
2085
+ v = rs.first[:j]
2086
+ v.should_not be_a_kind_of(Array)
2087
+ v.to_a.should be_a_kind_of(Array)
2088
+ v.should == j
2089
+ v.to_a.should == j
2090
+ @ds.delete
2091
+ @ds.insert(rs.first)
2092
+ @ds.all.should == rs
2093
+ end
2094
+ end
2095
+
2096
+ specify 'use json in bound variables' do
2097
+ @db.create_table!(:items){json :i}
2098
+ @ds.call(:insert, {:i=>Sequel.pg_json(@h)}, {:i=>:$i})
2099
+ @ds.get(:i).should == @h
2100
+ @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json(@h)).should == {:i=>@h}
2101
+ @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json({})).should == nil
2102
+ @ds.filter(Sequel.cast(:i, String)=>:$i).call(:delete, :i=>Sequel.pg_json(@h)).should == 1
2103
+
2104
+ @ds.call(:insert, {:i=>Sequel.pg_json(@a)}, {:i=>:$i})
2105
+ @ds.get(:i).should == @a
2106
+ @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json(@a)).should == {:i=>@a}
2107
+ @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json([])).should == nil
2108
+
2109
+ @ds.delete
2110
+ @ds.call(:insert, {:i=>Sequel.pg_json('a'=>nil)}, {:i=>:$i})
2111
+ @ds.get(:i).should == Sequel.pg_json('a'=>nil)
2112
+
2113
+ @db.create_table!(:items){column :i, 'json[]'}
2114
+ j = Sequel.pg_array([Sequel.pg_json('a'=>1), Sequel.pg_json(['b', 2])], :text)
2115
+ @ds.call(:insert, {:i=>j}, {:i=>:$i})
2116
+ @ds.get(:i).should == j
2117
+ @ds.filter(Sequel.cast(:i, 'text[]')=>:$i).call(:first, :i=>j).should == {:i=>j}
2118
+ @ds.filter(Sequel.cast(:i, 'text[]')=>:$i).call(:first, :i=>Sequel.pg_array([])).should == nil
2119
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2120
+
2121
+ specify 'with models' do
2122
+ @db.create_table!(:items) do
2123
+ primary_key :id
2124
+ json :h
2125
+ end
2126
+ c = Class.new(Sequel::Model(@db[:items]))
2127
+ c.plugin :pg_typecast_on_load, :h unless @native
2128
+ c.create(:h=>Sequel.pg_json(@h)).h.should == @h
2129
+ c.create(:h=>Sequel.pg_json(@a)).h.should == @a
2130
+ end
2131
+ end if POSTGRES_DB.server_version >= 90200
2132
+
2133
+ describe 'PostgreSQL inet/cidr types' do
2134
+ ipv6_broken = (IPAddr.new('::1'); false) rescue true
2135
+
2136
+ before(:all) do
2137
+ @db = POSTGRES_DB
2138
+ @db.extension :pg_array, :pg_inet
2139
+ @ds = @db[:items]
2140
+ @v4 = '127.0.0.1'
2141
+ @v4nm = '127.0.0.0/8'
2142
+ @v6 = '2001:4f8:3:ba:2e0:81ff:fe22:d1f1'
2143
+ @v6nm = '2001:4f8:3:ba::/64'
2144
+ @ipv4 = IPAddr.new(@v4)
2145
+ @ipv4nm = IPAddr.new(@v4nm)
2146
+ unless ipv6_broken
2147
+ @ipv6 = IPAddr.new(@v6)
2148
+ @ipv6nm = IPAddr.new(@v6nm)
2149
+ end
2150
+ @native = POSTGRES_DB.adapter_scheme == :postgres
2151
+ end
2152
+ after do
2153
+ @db.drop_table?(:items)
2154
+ end
2155
+
2156
+ specify 'insert and retrieve inet/cidr values' do
2157
+ @db.create_table!(:items){inet :i; cidr :c}
2158
+ @ds.insert(@ipv4, @ipv4nm)
2159
+ @ds.count.should == 1
2160
+ if @native
2161
+ rs = @ds.all
2162
+ rs.first[:i].should == @ipv4
2163
+ rs.first[:c].should == @ipv4nm
2164
+ rs.first[:i].should be_a_kind_of(IPAddr)
2165
+ rs.first[:c].should be_a_kind_of(IPAddr)
2166
+ @ds.delete
2167
+ @ds.insert(rs.first)
2168
+ @ds.all.should == rs
2169
+ end
2170
+
2171
+ unless ipv6_broken
2172
+ @ds.delete
2173
+ @ds.insert(@ipv6, @ipv6nm)
2174
+ @ds.count.should == 1
2175
+ if @native
2176
+ rs = @ds.all
2177
+ v = rs.first[:j]
2178
+ rs.first[:i].should == @ipv6
2179
+ rs.first[:c].should == @ipv6nm
2180
+ rs.first[:i].should be_a_kind_of(IPAddr)
2181
+ rs.first[:c].should be_a_kind_of(IPAddr)
2182
+ @ds.delete
2183
+ @ds.insert(rs.first)
2184
+ @ds.all.should == rs
2185
+ end
2186
+ end
2187
+ end
2188
+
2189
+ specify 'insert and retrieve inet/cidr/macaddr array values' do
2190
+ @db.create_table!(:items){column :i, 'inet[]'; column :c, 'cidr[]'; column :m, 'macaddr[]'}
2191
+ @ds.insert(Sequel.pg_array([@ipv4], 'inet'), Sequel.pg_array([@ipv4nm], 'cidr'), Sequel.pg_array(['12:34:56:78:90:ab'], 'macaddr'))
2192
+ @ds.count.should == 1
2193
+ if @native
2194
+ rs = @ds.all
2195
+ rs.first.values.all?{|c| c.is_a?(Sequel::Postgres::PGArray)}.should be_true
2196
+ rs.first[:i].first.should == @ipv4
2197
+ rs.first[:c].first.should == @ipv4nm
2198
+ rs.first[:m].first.should == '12:34:56:78:90:ab'
2199
+ rs.first[:i].first.should be_a_kind_of(IPAddr)
2200
+ rs.first[:c].first.should be_a_kind_of(IPAddr)
2201
+ @ds.delete
2202
+ @ds.insert(rs.first)
2203
+ @ds.all.should == rs
2204
+ end
2205
+ end
2206
+
2207
+ specify 'use ipaddr in bound variables' do
2208
+ @db.create_table!(:items){inet :i; cidr :c}
2209
+
2210
+ @ds.call(:insert, {:i=>@ipv4, :c=>@ipv4nm}, {:i=>:$i, :c=>:$c})
2211
+ @ds.get(:i).should == @ipv4
2212
+ @ds.get(:c).should == @ipv4nm
2213
+ @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv4, :c=>@ipv4nm).should == {:i=>@ipv4, :c=>@ipv4nm}
2214
+ @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv6, :c=>@ipv6nm).should == nil
2215
+ @ds.filter(:i=>:$i, :c=>:$c).call(:delete, :i=>@ipv4, :c=>@ipv4nm).should == 1
2216
+
2217
+ unless ipv6_broken
2218
+ @ds.call(:insert, {:i=>@ipv6, :c=>@ipv6nm}, {:i=>:$i, :c=>:$c})
2219
+ @ds.get(:i).should == @ipv6
2220
+ @ds.get(:c).should == @ipv6nm
2221
+ @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv6, :c=>@ipv6nm).should == {:i=>@ipv6, :c=>@ipv6nm}
2222
+ @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv4, :c=>@ipv4nm).should == nil
2223
+ @ds.filter(:i=>:$i, :c=>:$c).call(:delete, :i=>@ipv6, :c=>@ipv6nm).should == 1
2224
+ end
2225
+
2226
+ @db.create_table!(:items){column :i, 'inet[]'; column :c, 'cidr[]'; column :m, 'macaddr[]'}
2227
+ @ds.call(:insert, {:i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']}, {:i=>:$i, :c=>:$c, :m=>:$m})
2228
+ @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:first, :i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']).should == {:i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']}
2229
+ @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:first, :i=>[], :c=>[], :m=>[]).should == nil
2230
+ @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:delete, :i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']).should == 1
2231
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2232
+
2233
+ specify 'with models' do
2234
+ @db.create_table!(:items) do
2235
+ primary_key :id
2236
+ inet :i
2237
+ cidr :c
2238
+ end
2239
+ c = Class.new(Sequel::Model(@db[:items]))
2240
+ c.plugin :pg_typecast_on_load, :i, :c unless @native
2241
+ c.create(:i=>@v4, :c=>@v4nm).values.values_at(:i, :c).should == [@ipv4, @ipv4nm]
2242
+ unless ipv6_broken
2243
+ c.create(:i=>@ipv6, :c=>@ipv6nm).values.values_at(:i, :c).should == [@ipv6, @ipv6nm]
2244
+ end
2245
+ end
2246
+ end
2247
+
2248
+ describe 'PostgreSQL range types' do
2249
+ before(:all) do
2250
+ @db = POSTGRES_DB
2251
+ @db.extension :pg_array, :pg_range
2252
+ @ds = @db[:items]
2253
+ @map = {:i4=>'int4range', :i8=>'int8range', :n=>'numrange', :d=>'daterange', :t=>'tsrange', :tz=>'tstzrange'}
2254
+ @r = {:i4=>1...2, :i8=>2...3, :n=>BigDecimal.new('1.0')..BigDecimal.new('2.0'), :d=>Date.today...(Date.today+1), :t=>Time.local(2011, 1)..Time.local(2011, 2), :tz=>Time.local(2011, 1)..Time.local(2011, 2)}
2255
+ @ra = {}
2256
+ @pgr = {}
2257
+ @pgra = {}
2258
+ @r.each{|k, v| @ra[k] = Sequel.pg_array([v], @map[k])}
2259
+ @r.each{|k, v| @pgr[k] = Sequel.pg_range(v)}
2260
+ @r.each{|k, v| @pgra[k] = Sequel.pg_array([Sequel.pg_range(v)], @map[k])}
2261
+ @native = POSTGRES_DB.adapter_scheme == :postgres
2262
+ end
2263
+ after do
2264
+ @db.drop_table?(:items)
2265
+ end
2266
+
2267
+ specify 'insert and retrieve range type values' do
2268
+ @db.create_table!(:items){int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
2269
+ [@r, @pgr].each do |input|
2270
+ h = {}
2271
+ input.each{|k, v| h[k] = Sequel.cast(v, @map[k])}
2272
+ @ds.insert(h)
2273
+ @ds.count.should == 1
2274
+ if @native
2275
+ rs = @ds.all
2276
+ rs.first.each do |k, v|
2277
+ v.should_not be_a_kind_of(Range)
2278
+ v.to_range.should be_a_kind_of(Range)
2279
+ v.should == @r[k]
2280
+ v.to_range.should == @r[k]
2281
+ end
2282
+ @ds.delete
2283
+ @ds.insert(rs.first)
2284
+ @ds.all.should == rs
2285
+ end
2286
+ @ds.delete
2287
+ end
2288
+ end
2289
+
2290
+ specify 'insert and retrieve arrays of range type values' do
2291
+ @db.create_table!(:items){column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
2292
+ [@ra, @pgra].each do |input|
2293
+ @ds.insert(input)
2294
+ @ds.count.should == 1
2295
+ if @native
2296
+ rs = @ds.all
2297
+ rs.first.each do |k, v|
2298
+ v.should_not be_a_kind_of(Array)
2299
+ v.to_a.should be_a_kind_of(Array)
2300
+ v.first.should_not be_a_kind_of(Range)
2301
+ v.first.to_range.should be_a_kind_of(Range)
2302
+ v.should == @ra[k].to_a
2303
+ v.first.should == @r[k]
2304
+ end
2305
+ @ds.delete
2306
+ @ds.insert(rs.first)
2307
+ @ds.all.should == rs
2308
+ end
2309
+ @ds.delete
2310
+ end
2311
+ end
2312
+
2313
+ specify 'use range types in bound variables' do
2314
+ @db.create_table!(:items){int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
2315
+ h = {}
2316
+ @r.keys.each{|k| h[k] = :"$#{k}"}
2317
+ r2 = {}
2318
+ @r.each{|k, v| r2[k] = Range.new(v.begin, v.end+2)}
2319
+ @ds.call(:insert, @r, h)
2320
+ @ds.first.should == @r
2321
+ @ds.filter(h).call(:first, @r).should == @r
2322
+ @ds.filter(h).call(:first, @pgr).should == @r
2323
+ @ds.filter(h).call(:first, r2).should == nil
2324
+ @ds.filter(h).call(:delete, @r).should == 1
2325
+
2326
+ @db.create_table!(:items){column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
2327
+ @r.each{|k, v| r2[k] = [Range.new(v.begin, v.end+2)]}
2328
+ @ds.call(:insert, @ra, h)
2329
+ @ds.filter(h).call(:first, @ra).each{|k, v| v.should == @ra[k].to_a}
2330
+ @ds.filter(h).call(:first, @pgra).each{|k, v| v.should == @ra[k].to_a}
2331
+ @ds.filter(h).call(:first, r2).should == nil
2332
+ @ds.filter(h).call(:delete, @ra).should == 1
2333
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2334
+
2335
+ specify 'with models' do
2336
+ @db.create_table!(:items){primary_key :id; int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
2337
+ c = Class.new(Sequel::Model(@db[:items]))
2338
+ c.plugin :pg_typecast_on_load, :i4, :i8, :n, :d, :t, :tz unless @native
2339
+ v = c.create(@r).values
2340
+ v.delete(:id)
2341
+ v.should == @r
2342
+
2343
+ unless @db.adapter_scheme == :jdbc
2344
+ @db.create_table!(:items){primary_key :id; column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
2345
+ c = Class.new(Sequel::Model(@db[:items]))
2346
+ c.plugin :pg_typecast_on_load, :i4, :i8, :n, :d, :t, :tz unless @native
2347
+ v = c.create(@ra).values
2348
+ v.delete(:id)
2349
+ v.each{|k,v| v.should == @ra[k].to_a}
2350
+ end
2351
+ end
2352
+
2353
+ specify 'operations/functions with pg_range_ops' do
2354
+ Sequel.extension :pg_range_ops
2355
+
2356
+ @db.get(Sequel.pg_range(1..5, :int4range).op.contains(2..4)).should be_true
2357
+ @db.get(Sequel.pg_range(1..5, :int4range).op.contains(3..6)).should be_false
2358
+ @db.get(Sequel.pg_range(1..5, :int4range).op.contains(0..6)).should be_false
2359
+
2360
+ @db.get(Sequel.pg_range(1..5, :int4range).op.contained_by(0..6)).should be_true
2361
+ @db.get(Sequel.pg_range(1..5, :int4range).op.contained_by(3..6)).should be_false
2362
+ @db.get(Sequel.pg_range(1..5, :int4range).op.contained_by(2..4)).should be_false
2363
+
2364
+ @db.get(Sequel.pg_range(1..5, :int4range).op.overlaps(5..6)).should be_true
2365
+ @db.get(Sequel.pg_range(1...5, :int4range).op.overlaps(5..6)).should be_false
2366
+
2367
+ @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(6..10)).should be_true
2368
+ @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(5..10)).should be_false
2369
+ @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(-1..0)).should be_false
2370
+ @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(-1..3)).should be_false
2371
+
2372
+ @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(6..10)).should be_false
2373
+ @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(5..10)).should be_false
2374
+ @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(-1..0)).should be_true
2375
+ @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(-1..3)).should be_false
2376
+
2377
+ @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(6..10)).should be_true
2378
+ @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(5..10)).should be_true
2379
+ @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(-1..0)).should be_false
2380
+ @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(-1..3)).should be_false
2381
+
2382
+ @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(6..10)).should be_false
2383
+ @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(5..10)).should be_false
2384
+ @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(-1..0)).should be_true
2385
+ @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(-1..3)).should be_true
2386
+
2387
+ @db.get(Sequel.pg_range(1..5, :int4range).op.adjacent_to(6..10)).should be_true
2388
+ @db.get(Sequel.pg_range(1...5, :int4range).op.adjacent_to(6..10)).should be_false
2389
+
2390
+ @db.get((Sequel.pg_range(1..5, :int4range).op + (6..10)).adjacent_to(6..10)).should be_false
2391
+ @db.get((Sequel.pg_range(1..5, :int4range).op + (6..10)).adjacent_to(11..20)).should be_true
2392
+
2393
+ @db.get((Sequel.pg_range(1..5, :int4range).op * (2..6)).adjacent_to(6..10)).should be_true
2394
+ @db.get((Sequel.pg_range(1..4, :int4range).op * (2..6)).adjacent_to(6..10)).should be_false
2395
+
2396
+ @db.get((Sequel.pg_range(1..5, :int4range).op - (2..6)).adjacent_to(2..10)).should be_true
2397
+ @db.get((Sequel.pg_range(0..4, :int4range).op - (3..6)).adjacent_to(4..10)).should be_false
2398
+
2399
+ @db.get(Sequel.pg_range(0..4, :int4range).op.lower).should == 0
2400
+ @db.get(Sequel.pg_range(0..4, :int4range).op.upper).should == 5
2401
+
2402
+ @db.get(Sequel.pg_range(0..4, :int4range).op.isempty).should be_false
2403
+ @db.get(Sequel::Postgres::PGRange.empty(:int4range).op.isempty).should be_true
2404
+
2405
+ @db.get(Sequel.pg_range(1..5, :numrange).op.lower_inc).should be_true
2406
+ @db.get(Sequel::Postgres::PGRange.new(1, 5, :exclude_begin=>true, :db_type=>:numrange).op.lower_inc).should be_false
2407
+
2408
+ @db.get(Sequel.pg_range(1..5, :numrange).op.upper_inc).should be_true
2409
+ @db.get(Sequel.pg_range(1...5, :numrange).op.upper_inc).should be_false
2410
+
2411
+ @db.get(Sequel::Postgres::PGRange.new(1, 5, :db_type=>:int4range).op.lower_inf).should be_false
2412
+ @db.get(Sequel::Postgres::PGRange.new(nil, 5, :db_type=>:int4range).op.lower_inf).should be_true
2413
+
2414
+ @db.get(Sequel::Postgres::PGRange.new(1, 5, :db_type=>:int4range).op.upper_inf).should be_false
2415
+ @db.get(Sequel::Postgres::PGRange.new(1, nil, :db_type=>:int4range).op.upper_inf).should be_true
2416
+ end
2417
+ end if POSTGRES_DB.server_version >= 90200
2418
+
2419
+ describe 'PostgreSQL interval types' do
2420
+ before(:all) do
2421
+ @db = POSTGRES_DB
2422
+ @db.extension :pg_array, :pg_interval
2423
+ @ds = @db[:items]
2424
+ @native = POSTGRES_DB.adapter_scheme == :postgres
2425
+ end
2426
+ after(:all) do
2427
+ Sequel::Postgres::PG_TYPES.delete(1186)
2428
+ end
2429
+ after do
2430
+ @db.drop_table?(:items)
2431
+ end
2432
+
2433
+ specify 'insert and retrieve interval values' do
2434
+ @db.create_table!(:items){interval :i}
2435
+ [
2436
+ ['0', '00:00:00', 0, [[:seconds, 0]]],
2437
+ ['1 microsecond', '00:00:00.000001', 0.000001, [[:seconds, 0.000001]]],
2438
+ ['1 millisecond', '00:00:00.001', 0.001, [[:seconds, 0.001]]],
2439
+ ['1 second', '00:00:01', 1, [[:seconds, 1]]],
2440
+ ['1 minute', '00:01:00', 60, [[:seconds, 60]]],
2441
+ ['1 hour', '01:00:00', 3600, [[:seconds, 3600]]],
2442
+ ['1 day', '1 day', 86400, [[:days, 1]]],
2443
+ ['1 week', '7 days', 86400*7, [[:days, 7]]],
2444
+ ['1 month', '1 mon', 86400*30, [[:months, 1]]],
2445
+ ['1 year', '1 year', 31557600, [[:years, 1]]],
2446
+ ['1 decade', '10 years', 31557600*10, [[:years, 10]]],
2447
+ ['1 century', '100 years', 31557600*100, [[:years, 100]]],
2448
+ ['1 millennium', '1000 years', 31557600*1000, [[:years, 1000]]],
2449
+ ['1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds', '1 year 2 mons 25 days 05:06:07', 31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]]],
2450
+ ['-1 year +2 months -3 weeks +4 days -5 hours +6 minutes -7 seconds', '-10 mons -17 days -04:54:07', -10*86400*30 - 3*86400*7 + 4*86400 - 5*3600 + 6*60 - 7, [[:months, -10], [:days, -17], [:seconds, -17647]]],
2451
+ ['+2 years -1 months +3 weeks -4 days +5 hours -6 minutes +7 seconds', '1 year 11 mons 17 days 04:54:07', 31557600 + 11*86400*30 + 3*86400*7 - 4*86400 + 5*3600 - 6*60 + 7, [[:years, 1], [:months, 11], [:days, 17], [:seconds, 17647]]],
2452
+ ].each do |instr, outstr, value, parts|
2453
+ @ds.insert(instr)
2454
+ @ds.count.should == 1
2455
+ if @native
2456
+ @ds.get(Sequel.cast(:i, String)).should == outstr
2457
+ rs = @ds.all
2458
+ rs.first[:i].is_a?(ActiveSupport::Duration).should be_true
2459
+ rs.first[:i].should == ActiveSupport::Duration.new(value, parts)
2460
+ rs.first[:i].parts.sort_by{|k,v| k.to_s}.should == parts.sort_by{|k,v| k.to_s}
2461
+ @ds.delete
2462
+ @ds.insert(rs.first)
2463
+ @ds.all.should == rs
2464
+ end
2465
+ @ds.delete
2466
+ end
2467
+ end
2468
+
2469
+ specify 'insert and retrieve interval array values' do
2470
+ @db.create_table!(:items){column :i, 'interval[]'}
2471
+ @ds.insert(Sequel.pg_array(['1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds'], 'interval'))
2472
+ @ds.count.should == 1
2473
+ if @native
2474
+ rs = @ds.all
2475
+ rs.first[:i].is_a?(Sequel::Postgres::PGArray).should be_true
2476
+ rs.first[:i].first.is_a?(ActiveSupport::Duration).should be_true
2477
+ rs.first[:i].first.should == ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
2478
+ rs.first[:i].first.parts.sort_by{|k,v| k.to_s}.should == [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]].sort_by{|k,v| k.to_s}
2479
+ @ds.delete
2480
+ @ds.insert(rs.first)
2481
+ @ds.all.should == rs
2482
+ end
2483
+ end
2484
+
2485
+ specify 'use intervals in bound variables' do
2486
+ @db.create_table!(:items){interval :i}
2487
+ @ds.insert('1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds')
2488
+ d = @ds.get(:i)
2489
+ @ds.delete
2490
+
2491
+ @ds.call(:insert, {:i=>d}, {:i=>:$i})
2492
+ @ds.get(:i).should == d
2493
+ @ds.filter(:i=>:$i).call(:first, :i=>d).should == {:i=>d}
2494
+ @ds.filter(:i=>:$i).call(:first, :i=>'0').should == nil
2495
+ @ds.filter(:i=>:$i).call(:delete, :i=>d).should == 1
2496
+
2497
+ @db.create_table!(:items){column :i, 'interval[]'}
2498
+ @ds.call(:insert, {:i=>[d]}, {:i=>:$i})
2499
+ @ds.filter(:i=>:$i).call(:first, :i=>[d]).should == {:i=>[d]}
2500
+ @ds.filter(:i=>:$i).call(:first, :i=>[]).should == nil
2501
+ @ds.filter(:i=>:$i).call(:delete, :i=>[d]).should == 1
2502
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2503
+
2504
+ specify 'with models' do
2505
+ @db.create_table!(:items) do
2506
+ primary_key :id
2507
+ interval :i
2508
+ end
2509
+ c = Class.new(Sequel::Model(@db[:items]))
2510
+ c.plugin :pg_typecast_on_load, :i, :c unless @native
2511
+ v = c.create(:i=>'1 year 2 mons 25 days 05:06:07').i
2512
+ v.is_a?(ActiveSupport::Duration).should be_true
2513
+ v.should == ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
2514
+ v.parts.sort_by{|k,v| k.to_s}.should == [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]].sort_by{|k,v| k.to_s}
2515
+ end
2516
+ end if ((require 'active_support/duration'; require 'active_support/inflector'; require 'active_support/core_ext/string/inflections'; true) rescue false)
2517
+
2518
+ describe 'PostgreSQL row-valued/composite types' do
2519
+ before(:all) do
2520
+ @db = POSTGRES_DB
2521
+ Sequel.extension :pg_array_ops, :pg_row_ops
2522
+ @db.extension :pg_array, :pg_row
2523
+ @ds = @db[:person]
2524
+
2525
+ @db.create_table!(:address) do
2526
+ String :street
2527
+ String :city
2528
+ String :zip
2529
+ end
2530
+ @db.create_table!(:person) do
2531
+ Integer :id
2532
+ address :address
2533
+ end
2534
+ @db.create_table!(:company) do
2535
+ Integer :id
2536
+ column :employees, 'person[]'
2537
+ end
2538
+ @db.register_row_type(:address)
2539
+ @db.register_row_type(:person)
2540
+ @db.register_row_type(:company)
2541
+
2542
+ @native = POSTGRES_DB.adapter_scheme == :postgres
2543
+ end
2544
+ after(:all) do
2545
+ @db.drop_table?(:company, :person, :address)
2546
+ @db.row_types.clear
2547
+ @db.reset_conversion_procs if @native
2548
+ end
2549
+ after do
2550
+ [:company, :person, :address].each{|t| @db[t].delete}
2551
+ end
2552
+
2553
+ specify 'insert and retrieve row types' do
2554
+ @ds.insert(:id=>1, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))
2555
+ @ds.count.should == 1
2556
+ if @native
2557
+ # Single row valued type
2558
+ rs = @ds.all
2559
+ v = rs.first[:address]
2560
+ v.should_not be_a_kind_of(Hash)
2561
+ v.to_hash.should be_a_kind_of(Hash)
2562
+ v.to_hash.should == {:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}
2563
+ @ds.delete
2564
+ @ds.insert(rs.first)
2565
+ @ds.all.should == rs
2566
+
2567
+ # Nested row value type
2568
+ p = @ds.get(:person)
2569
+ p[:id].should == 1
2570
+ p[:address].should == v
2571
+ end
2572
+ end
2573
+
2574
+ specify 'insert and retrieve arrays of row types' do
2575
+ @ds = @db[:company]
2576
+ @ds.insert(:id=>1, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])]))
2577
+ @ds.count.should == 1
2578
+ if @native
2579
+ v = @ds.get(:company)
2580
+ v.should_not be_a_kind_of(Hash)
2581
+ v.to_hash.should be_a_kind_of(Hash)
2582
+ v[:id].should == 1
2583
+ employees = v[:employees]
2584
+ employees.should_not be_a_kind_of(Array)
2585
+ employees.to_a.should be_a_kind_of(Array)
2586
+ employees.should == [{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}]
2587
+ @ds.delete
2588
+ @ds.insert(v[:id], v[:employees])
2589
+ @ds.get(:company).should == v
2590
+ end
2591
+ end
2592
+
2593
+ specify 'use row types in bound variables' do
2594
+ @ds.call(:insert, {:address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])}, {:address=>:$address, :id=>1})
2595
+ @ds.get(:address).should == {:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}
2596
+ @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))[:id].should == 1
2597
+ @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])).should == nil
2598
+
2599
+ @ds.delete
2600
+ @ds.call(:insert, {:address=>Sequel.pg_row([nil, nil, nil])}, {:address=>:$address, :id=>1})
2601
+ @ds.get(:address).should == {:street=>nil, :city=>nil, :zip=>nil}
2602
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2603
+
2604
+ specify 'use arrays of row types in bound variables' do
2605
+ @ds = @db[:company]
2606
+ @ds.call(:insert, {:employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])])}, {:employees=>:$employees, :id=>1})
2607
+ @ds.get(:company).should == {:id=>1, :employees=>[{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}]}
2608
+ @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])]))[:id].should == 1
2609
+ @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])])])).should == nil
2610
+
2611
+
2612
+ @ds.delete
2613
+ @ds.call(:insert, {:employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row([nil, nil, nil])])])}, {:employees=>:$employees, :id=>1})
2614
+ @ds.get(:employees).should == [{:address=>{:city=>nil, :zip=>nil, :street=>nil}, :id=>1}]
2615
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2616
+
2617
+ specify 'operations/functions with pg_row_ops' do
2618
+ @ds.insert(:id=>1, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))
2619
+ @ds.get(Sequel.pg_row(:address)[:street]).should == '123 Sesame St'
2620
+ @ds.get(Sequel.pg_row(:address)[:city]).should == 'Somewhere'
2621
+ @ds.get(Sequel.pg_row(:address)[:zip]).should == '12345'
2622
+
2623
+ @ds = @db[:company]
2624
+ @ds.insert(:id=>1, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])]))
2625
+ @ds.get(Sequel.pg_row(:company)[:id]).should == 1
2626
+ if @native
2627
+ @ds.get(Sequel.pg_row(:company)[:employees]).should == [{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}]
2628
+ @ds.get(Sequel.pg_row(:company)[:employees][1]).should == {:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}
2629
+ @ds.get(Sequel.pg_row(:company)[:employees][1][:address]).should == {:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}
2630
+ end
2631
+ @ds.get(Sequel.pg_row(:company)[:employees][1][:id]).should == 1
2632
+ @ds.get(Sequel.pg_row(:company)[:employees][1][:address][:street]).should == '123 Sesame St'
2633
+ @ds.get(Sequel.pg_row(:company)[:employees][1][:address][:city]).should == 'Somewhere'
2634
+ @ds.get(Sequel.pg_row(:company)[:employees][1][:address][:zip]).should == '12345'
2635
+ end
2636
+
2637
+ context "#splat and #*" do
2638
+ before(:all) do
2639
+ @db.create_table!(:a){Integer :a}
2640
+ @db.create_table!(:b){a :b; Integer :a}
2641
+ @db.register_row_type(:a)
2642
+ @db.register_row_type(:b)
2643
+ @db[:b].insert(:a=>1, :b=>@db.row_type(:a, [2]))
2644
+ end
2645
+ after(:all) do
2646
+ @db.drop_table?(:b, :a)
2647
+ end
2648
+
2649
+ specify "splat should reference the table type" do
2650
+ @db[:b].select(:a).first.should == {:a=>1}
2651
+ @db[:b].select(:b__a).first.should == {:a=>1}
2652
+ @db[:b].select(Sequel.pg_row(:b)[:a]).first.should == {:a=>2}
2653
+ @db[:b].select(Sequel.pg_row(:b).splat[:a]).first.should == {:a=>1}
2654
+
2655
+ if @native
2656
+ @db[:b].select(:b).first.should == {:b=>{:a=>2}}
2657
+ @db[:b].select(Sequel.pg_row(:b).splat).first.should == {:a=>1, :b=>{:a=>2}}
2658
+ @db[:b].select(Sequel.pg_row(:b).splat(:b)).first.should == {:b=>{:a=>1, :b=>{:a=>2}}}
2659
+ end
2660
+ end
2661
+
2662
+ specify "* should expand the table type into separate columns" do
2663
+ ds = @db[:b].select(Sequel.pg_row(:b).splat(:b)).from_self(:alias=>:t)
2664
+ if @native
2665
+ ds.first.should == {:b=>{:a=>1, :b=>{:a=>2}}}
2666
+ ds.select(Sequel.pg_row(:b).*).first.should == {:a=>1, :b=>{:a=>2}}
2667
+ ds.select(Sequel.pg_row(:b)[:b]).first.should == {:b=>{:a=>2}}
2668
+ ds.select(Sequel.pg_row(:t__b).*).first.should == {:a=>1, :b=>{:a=>2}}
2669
+ ds.select(Sequel.pg_row(:t__b)[:b]).first.should == {:b=>{:a=>2}}
2670
+ end
2671
+ ds.select(Sequel.pg_row(:b)[:a]).first.should == {:a=>1}
2672
+ ds.select(Sequel.pg_row(:t__b)[:a]).first.should == {:a=>1}
2673
+ end
2674
+ end
2675
+
2676
+ context "with models" do
2677
+ before(:all) do
2678
+ class Address < Sequel::Model(:address)
2679
+ plugin :pg_row
2680
+ end
2681
+ class Person < Sequel::Model(:person)
2682
+ plugin :pg_row
2683
+ end
2684
+ class Company < Sequel::Model(:company)
2685
+ plugin :pg_row
2686
+ end
2687
+ @a = Address.new(:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345')
2688
+ @es = Sequel.pg_array([Person.new(:id=>1, :address=>@a)])
2689
+ end
2690
+ after(:all) do
2691
+ Object.send(:remove_const, :Address) rescue nil
2692
+ Object.send(:remove_const, :Person) rescue nil
2693
+ Object.send(:remove_const, :Company) rescue nil
2694
+ end
2695
+
2696
+ specify 'insert and retrieve row types as model objects' do
2697
+ @ds.insert(:id=>1, :address=>@a)
2698
+ @ds.count.should == 1
2699
+ if @native
2700
+ # Single row valued type
2701
+ rs = @ds.all
2702
+ v = rs.first[:address]
2703
+ v.should be_a_kind_of(Address)
2704
+ v.should == @a
2705
+ @ds.delete
2706
+ @ds.insert(rs.first)
2707
+ @ds.all.should == rs
2708
+
2709
+ # Nested row value type
2710
+ p = @ds.get(:person)
2711
+ p.should be_a_kind_of(Person)
2712
+ p.id.should == 1
2713
+ p.address.should be_a_kind_of(Address)
2714
+ p.address.should == @a
2715
+ end
2716
+ end
2717
+
2718
+ specify 'insert and retrieve arrays of row types as model objects' do
2719
+ @ds = @db[:company]
2720
+ @ds.insert(:id=>1, :employees=>@es)
2721
+ @ds.count.should == 1
2722
+ if @native
2723
+ v = @ds.get(:company)
2724
+ v.should be_a_kind_of(Company)
2725
+ v.id.should == 1
2726
+ employees = v[:employees]
2727
+ employees.should_not be_a_kind_of(Array)
2728
+ employees.to_a.should be_a_kind_of(Array)
2729
+ employees.should == @es
2730
+ @ds.delete
2731
+ @ds.insert(v.id, v.employees)
2732
+ @ds.get(:company).should == v
2733
+ end
2734
+ end
2735
+
2736
+ specify 'use model objects in bound variables' do
2737
+ @ds.call(:insert, {:address=>@a}, {:address=>:$address, :id=>1})
2738
+ @ds.get(:address).should == @a
2739
+ @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>@a)[:id].should == 1
2740
+ @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Address.new(:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12356')).should == nil
2741
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2742
+
2743
+ specify 'use arrays of model objects in bound variables' do
2744
+ @ds = @db[:company]
2745
+ @ds.call(:insert, {:employees=>@es}, {:employees=>:$employees, :id=>1})
2746
+ @ds.get(:company).should == Company.new(:id=>1, :employees=>@es)
2747
+ @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>@es)[:id].should == 1
2748
+ @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])])])).should == nil
2749
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2750
+
2751
+ specify 'model typecasting' do
2752
+ Person.plugin :pg_typecast_on_load, :address unless @native
2753
+ a = Address.new(:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345')
2754
+ o = Person.create(:id=>1, :address=>['123 Sesame St', 'Somewhere', '12345'])
2755
+ o.address.should == a
2756
+ o = Person.create(:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'})
2757
+ o.address.should == a
2758
+ o = Person.create(:id=>1, :address=>a)
2759
+ o.address.should == a
2760
+
2761
+ Company.plugin :pg_typecast_on_load, :employees unless @native
2762
+ e = Person.new(:id=>1, :address=>a)
2763
+ unless @db.adapter_scheme == :jdbc
2764
+ o = Company.create(:id=>1, :employees=>[{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}])
2765
+ o.employees.should == [e]
2766
+ o = Company.create(:id=>1, :employees=>[e])
2767
+ o.employees.should == [e]
2768
+ end
2769
+ end
2770
+ end
1009
2771
  end