sequel 3.21.0 → 3.39.0

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