sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -7,12 +7,7 @@ module Sequel
7
7
  module MSSQL
8
8
  module DatabaseMethods
9
9
  include Sequel::MSSQL::DatabaseMethods
10
- LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'
11
-
12
- # Return an instance of Sequel::ODBC::MSSQL::Dataset with the given opts.
13
- def dataset(opts=nil)
14
- Sequel::ODBC::MSSQL::Dataset.new(self, opts)
15
- end
10
+ LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'.freeze
16
11
 
17
12
  # Return the last inserted identity value.
18
13
  def execute_insert(sql, opts={})
@@ -33,9 +28,22 @@ module Sequel
33
28
  end
34
29
  end
35
30
  end
36
-
37
31
  class Dataset < ODBC::Dataset
38
32
  include Sequel::MSSQL::DatasetMethods
33
+
34
+ private
35
+
36
+ # Use ODBC format, not Microsoft format, as the ODBC layer does
37
+ # some translation.
38
+ def default_timestamp_format
39
+ TIMESTAMP_FORMAT
40
+ end
41
+
42
+ # Use ODBC format, not Microsoft format, as the ODBC layer does
43
+ # some translation.
44
+ def literal_date(v)
45
+ v.strftime(ODBC_DATE_FORMAT)
46
+ end
39
47
  end
40
48
  end
41
49
  end
@@ -15,9 +15,16 @@ module Sequel
15
15
  when 'mssql'
16
16
  Sequel.ts_require 'adapters/odbc/mssql'
17
17
  extend Sequel::ODBC::MSSQL::DatabaseMethods
18
+ @dataset_class = Sequel::ODBC::MSSQL::Dataset
19
+ set_mssql_unicode_strings
18
20
  when 'progress'
19
21
  Sequel.ts_require 'adapters/shared/progress'
20
22
  extend Sequel::Progress::DatabaseMethods
23
+ extend_datasets(Sequel::Progress::DatasetMethods)
24
+ when 'db2'
25
+ Sequel.ts_require 'adapters/shared/db2'
26
+ extend ::Sequel::DB2::DatabaseMethods
27
+ extend_datasets ::Sequel::DB2::DatasetMethods
21
28
  end
22
29
  end
23
30
 
@@ -41,17 +48,13 @@ module Sequel
41
48
  conn
42
49
  end
43
50
 
44
- def dataset(opts = nil)
45
- ODBC::Dataset.new(self, opts)
46
- end
47
-
48
51
  def execute(sql, opts={})
49
52
  synchronize(opts[:server]) do |conn|
50
53
  begin
51
54
  r = log_yield(sql){conn.run(sql)}
52
55
  yield(r) if block_given?
53
56
  rescue ::ODBC::Error, ArgumentError => e
54
- raise_error(e, :disconnect=>DISCONNECT_ERRORS.match(e.message))
57
+ raise_error(e)
55
58
  ensure
56
59
  r.drop if r
57
60
  end
@@ -64,7 +67,7 @@ module Sequel
64
67
  begin
65
68
  log_yield(sql){conn.do(sql)}
66
69
  rescue ::ODBC::Error, ArgumentError => e
67
- raise_error(e, :disconnect=>DISCONNECT_ERRORS.match(e.message))
70
+ raise_error(e)
68
71
  end
69
72
  end
70
73
  end
@@ -76,9 +79,17 @@ module Sequel
76
79
  :do
77
80
  end
78
81
 
82
+ def database_error_classes
83
+ [::ODBC::Error]
84
+ end
85
+
79
86
  def disconnect_connection(c)
80
87
  c.disconnect
81
88
  end
89
+
90
+ def disconnect_error?(e, opts)
91
+ super || (e.is_a?(::ODBC::Error) && DISCONNECT_ERRORS.match(e.message))
92
+ end
82
93
  end
83
94
 
84
95
  class Dataset < Sequel::Dataset
@@ -87,15 +98,23 @@ module Sequel
87
98
  ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
88
99
  TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S'}".freeze
89
100
 
101
+ Database::DatasetClass = self
102
+
90
103
  def fetch_rows(sql)
91
104
  execute(sql) do |s|
92
105
  i = -1
93
106
  cols = s.columns(true).map{|c| [output_identifier(c.name), i+=1]}
94
- @columns = cols.map{|c| c.at(0)}
107
+ columns = cols.map{|c| c.at(0)}
108
+ if opts[:offset] && offset_returns_row_number_column?
109
+ rn = row_number_column
110
+ columns.delete(rn)
111
+ end
112
+ @columns = columns
95
113
  if rows = s.fetch_all
96
114
  rows.each do |row|
97
115
  hash = {}
98
116
  cols.each{|n,i| hash[n] = convert_odbc_value(row[i])}
117
+ hash.delete(rn) if rn
99
118
  yield hash
100
119
  end
101
120
  end
@@ -114,9 +133,9 @@ module Sequel
114
133
  # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
115
134
  case v
116
135
  when ::ODBC::TimeStamp
117
- Sequel.database_to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
136
+ db.to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
118
137
  when ::ODBC::Time
119
- Sequel.database_to_application_timestamp([now.year, now.month, now.day, v.hour, v.minute, v.second])
138
+ Sequel::SQLTime.create(v.hour, v.minute, v.second)
120
139
  when ::ODBC::Date
121
140
  Date.new(v.year, v.month, v.day)
122
141
  else
@@ -15,10 +15,6 @@ module Sequel
15
15
  )
16
16
  end
17
17
 
18
- def dataset(opts = nil)
19
- OpenBase::Dataset.new(self, opts)
20
- end
21
-
22
18
  def execute(sql, opts={})
23
19
  synchronize(opts[:server]) do |conn|
24
20
  r = log_yield(sql){conn.execute(sql)}
@@ -36,7 +32,9 @@ module Sequel
36
32
  end
37
33
 
38
34
  class Dataset < Sequel::Dataset
39
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
35
+ SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
36
+
37
+ Database::DatasetClass = self
40
38
 
41
39
  def fetch_rows(sql)
42
40
  execute(sql) do |result|
@@ -13,6 +13,18 @@ module Sequel
13
13
  # ORA-03114: not connected to ORACLE
14
14
  CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
15
15
 
16
+ ORACLE_TYPES = {:blob=>lambda{|b| Sequel::SQL::Blob.new(b.read)}}
17
+
18
+ # Hash of conversion procs for this database.
19
+ attr_reader :conversion_procs
20
+
21
+ def initialize(opts={})
22
+ super
23
+ @autosequence = opts[:autosequence]
24
+ @primary_key_sequences = {}
25
+ @conversion_procs = ORACLE_TYPES.dup
26
+ end
27
+
16
28
  def connect(server)
17
29
  opts = server_opts(server)
18
30
  if opts[:database]
@@ -22,61 +34,150 @@ module Sequel
22
34
  dbname = opts[:host]
23
35
  end
24
36
  conn = OCI8.new(opts[:user], opts[:password], dbname, opts[:privilege])
37
+ conn.prefetch_rows = typecast_value_integer(opts[:prefetch_rows]) if opts[:prefetch_rows]
25
38
  conn.autocommit = true
26
39
  conn.non_blocking = true
40
+
41
+ # The ruby-oci8 gem which retrieves oracle columns with a type of
42
+ # DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE is complex based on the
43
+ # ruby version (1.9.2 or later) and Oracle version (9 or later)
44
+ # In the now standard case of 1.9.2 and Oracle 9 or later, the timezone
45
+ # is determined by the Oracle session timezone. Thus if the user
46
+ # requests Sequel provide UTC timezone to the application,
47
+ # we need to alter the session timezone to be UTC
48
+ if Sequel.application_timezone == :utc
49
+ conn.exec("ALTER SESSION SET TIME_ZONE='-00:00'")
50
+ end
51
+
52
+ class << conn
53
+ attr_reader :prepared_statements
54
+ end
55
+ conn.instance_variable_set(:@prepared_statements, {})
56
+
27
57
  conn
28
58
  end
29
-
30
- def dataset(opts = nil)
31
- Oracle::Dataset.new(self, opts)
59
+
60
+ def execute(sql, opts={}, &block)
61
+ _execute(nil, sql, opts, &block)
32
62
  end
63
+ alias do execute
33
64
 
34
- def schema_parse_table(table, opts={})
35
- ds = dataset
36
- ds.identifier_output_method = :downcase
37
- schema_and_table = "#{"#{quote_identifier(opts[:schema])}." if opts[:schema]}#{quote_identifier(table)}"
38
- table_schema = []
39
- metadata = transaction(opts){|conn| conn.describe_table(schema_and_table)}
40
- metadata.columns.each do |column|
41
- table_schema << [
42
- column.name.downcase.to_sym,
43
- {
44
- :type => column.data_type,
45
- :db_type => column.type_string.split(' ')[0],
46
- :type_string => column.type_string,
47
- :charset_form => column.charset_form,
48
- :char_used => column.char_used?,
49
- :char_size => column.char_size,
50
- :data_size => column.data_size,
51
- :precision => column.precision,
52
- :scale => column.scale,
53
- :fsprecision => column.fsprecision,
54
- :lfprecision => column.lfprecision,
55
- :allow_null => column.nullable?
56
- }
57
- ]
58
- end
59
- table_schema
65
+ def execute_insert(sql, opts={})
66
+ _execute(:insert, sql, opts)
60
67
  end
61
68
 
62
- def execute(sql, opts={})
69
+ private
70
+
71
+ def _execute(type, sql, opts={}, &block)
63
72
  synchronize(opts[:server]) do |conn|
64
73
  begin
65
- r = log_yield(sql){conn.exec(sql)}
66
- yield(r) if block_given?
67
- r
68
- rescue OCIException => e
69
- raise_error(e, :disconnect=>CONNECTION_ERROR_CODES.include?(e.code))
74
+ return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
75
+ if args = opts[:arguments]
76
+ r = conn.parse(sql)
77
+ args = cursor_bind_params(conn, r, args)
78
+ nr = log_yield(sql, args){r.exec}
79
+ r = nr unless block_given?
80
+ else
81
+ r = log_yield(sql){conn.exec(sql)}
82
+ end
83
+ if block_given?
84
+ begin
85
+ yield(r)
86
+ ensure
87
+ r.close
88
+ end
89
+ elsif type == :insert
90
+ last_insert_id(conn, opts)
91
+ else
92
+ r
93
+ end
94
+ rescue OCIException, RuntimeError => e
95
+ # ruby-oci8 is naughty and raises strings in some places
96
+ raise_error(e)
97
+ end
98
+ end
99
+ end
100
+
101
+ PS_TYPES = {'string'.freeze=>String, 'integer'.freeze=>Integer, 'float'.freeze=>Float,
102
+ 'decimal'.freeze=>Float, 'date'.freeze=>Time, 'datetime'.freeze=>Time,
103
+ 'time'.freeze=>Time, 'boolean'.freeze=>String, 'blob'.freeze=>OCI8::BLOB}
104
+ def cursor_bind_params(conn, cursor, args)
105
+ cursor
106
+ i = 0
107
+ args.map do |arg, type|
108
+ i += 1
109
+ case arg
110
+ when true
111
+ arg = 'Y'
112
+ when false
113
+ arg = 'N'
114
+ when BigDecimal
115
+ arg = arg.to_f
116
+ when ::Sequel::SQL::Blob
117
+ raise Error, "Sequel's oracle adapter does not currently support using a blob in a bound variable"
118
+ end
119
+ if t = PS_TYPES[type]
120
+ cursor.bind_param(i, arg, t)
121
+ else
122
+ cursor.bind_param(i, arg, arg.class)
123
+ end
124
+ arg
125
+ end
126
+ end
127
+
128
+ def execute_prepared_statement(conn, type, name, opts)
129
+ ps = prepared_statement(name)
130
+ sql = ps.prepared_sql
131
+ if cursora = conn.prepared_statements[name]
132
+ cursor, cursor_sql = cursora
133
+ if cursor_sql != sql
134
+ cursor.close
135
+ cursor = nil
136
+ end
137
+ end
138
+ unless cursor
139
+ cursor = log_yield("PREPARE #{name}: #{sql}"){conn.parse(sql)}
140
+ conn.prepared_statements[name] = [cursor, sql]
141
+ end
142
+ args = cursor_bind_params(conn, cursor, opts[:arguments])
143
+ log_sql = "EXECUTE #{name}"
144
+ if ps.log_sql
145
+ log_sql << " ("
146
+ log_sql << sql
147
+ log_sql << ")"
148
+ end
149
+ r = log_yield(log_sql, args){cursor.exec}
150
+ if block_given?
151
+ yield(cursor)
152
+ elsif type == :insert
153
+ last_insert_id(conn, opts)
154
+ else
155
+ r
156
+ end
157
+ end
158
+
159
+ def last_insert_id(conn, opts)
160
+ unless sequence = opts[:sequence]
161
+ if t = opts[:table]
162
+ sequence = sequence_for_table(t)
163
+ end
164
+ end
165
+ if sequence
166
+ sql = "SELECT #{literal(sequence)}.currval FROM dual"
167
+ begin
168
+ cursor = log_yield(sql){conn.exec(sql)}
169
+ row = cursor.fetch
170
+ row.each{|v| return (v.to_i if v)}
171
+ rescue OCIError
172
+ nil
173
+ ensure
174
+ cursor.close if cursor
70
175
  end
71
176
  end
72
177
  end
73
- alias_method :do, :execute
74
178
 
75
- private
76
-
77
179
  def begin_transaction(conn, opts={})
78
180
  log_yield(TRANSACTION_BEGIN){conn.autocommit = false}
79
- conn
80
181
  end
81
182
 
82
183
  def commit_transaction(conn, opts={})
@@ -88,49 +189,244 @@ module Sequel
88
189
  rescue OCIInvalidHandle
89
190
  nil
90
191
  end
192
+
193
+ def disconnect_error?(e, opts)
194
+ super || (e.is_a?(::OCIError) && CONNECTION_ERROR_CODES.include?(e.code))
195
+ end
91
196
 
92
- def remove_transaction(conn)
93
- conn.autocommit = true if conn
197
+ def oracle_column_type(h)
198
+ case h[:oci8_type]
199
+ when :number
200
+ case h[:scale]
201
+ when 0
202
+ :integer
203
+ when -127
204
+ :float
205
+ else
206
+ :decimal
207
+ end
208
+ when :date
209
+ :datetime
210
+ else
211
+ schema_column_type(h[:db_type])
212
+ end
213
+ end
214
+
215
+ def remove_transaction(conn, committed)
216
+ conn.autocommit = true
217
+ ensure
94
218
  super
95
219
  end
96
220
 
97
221
  def rollback_transaction(conn, opts={})
98
222
  log_yield(TRANSACTION_ROLLBACK){conn.rollback}
99
223
  end
224
+
225
+ def schema_parse_table(table, opts={})
226
+ schema, table = schema_and_table(table)
227
+ schema ||= opts[:schema]
228
+ schema_and_table = if ds = opts[:dataset]
229
+ ds.literal(schema ? SQL::QualifiedIdentifier.new(schema, table) : SQL::Identifier.new(table))
230
+ else
231
+ "#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
232
+ end
233
+ table_schema = []
234
+ m = output_identifier_meth(ds)
235
+ im = input_identifier_meth(ds)
236
+
237
+ # Primary Keys
238
+ ds = metadata_dataset.from(:all_constraints___cons, :all_cons_columns___cols).
239
+ where(:cols__table_name=>im.call(table), :cons__constraint_type=>'P',
240
+ :cons__constraint_name=>:cols__constraint_name, :cons__owner=>:cols__owner)
241
+ ds = ds.where(:cons__owner=>im.call(schema)) if schema
242
+ pks = ds.select_map(:cols__column_name)
243
+
244
+ # Default values
245
+ defaults = begin
246
+ metadata_dataset.from(:user_tab_cols).
247
+ where(:table_name=>im.call(table)).
248
+ to_hash(:column_name, :data_default)
249
+ rescue DatabaseError
250
+ {}
251
+ end
252
+
253
+ metadata = synchronize(opts[:server]) do |conn|
254
+ begin
255
+ log_yield("Connection.describe_table"){conn.describe_table(schema_and_table)}
256
+ rescue OCIError => e
257
+ raise_error(e)
258
+ end
259
+ end
260
+ metadata.columns.each do |column|
261
+ h = {
262
+ :primary_key => pks.include?(column.name),
263
+ :default => defaults[column.name],
264
+ :oci8_type => column.data_type,
265
+ :db_type => column.type_string.split(' ')[0],
266
+ :type_string => column.type_string,
267
+ :charset_form => column.charset_form,
268
+ :char_used => column.char_used?,
269
+ :char_size => column.char_size,
270
+ :data_size => column.data_size,
271
+ :precision => column.precision,
272
+ :scale => column.scale,
273
+ :fsprecision => column.fsprecision,
274
+ :lfprecision => column.lfprecision,
275
+ :allow_null => column.nullable?
276
+ }
277
+ h[:type] = oracle_column_type(h)
278
+ table_schema << [m.call(column.name), h]
279
+ end
280
+ table_schema
281
+ end
100
282
  end
101
283
 
102
284
  class Dataset < Sequel::Dataset
103
285
  include DatasetMethods
104
286
 
287
+ Database::DatasetClass = self
288
+
289
+ PREPARED_ARG_PLACEHOLDER = ':'.freeze
290
+
291
+ # Oracle already supports named bind arguments, so use directly.
292
+ module ArgumentMapper
293
+ include Sequel::Dataset::ArgumentMapper
294
+
295
+ protected
296
+
297
+ # Return a hash with the same values as the given hash,
298
+ # but with the keys converted to strings.
299
+ def map_to_prepared_args(bind_vars)
300
+ prepared_args.map{|v, t| [bind_vars[v], t]}
301
+ end
302
+
303
+ private
304
+
305
+ # Oracle uses a : before the name of the argument for named
306
+ # arguments.
307
+ def prepared_arg(k)
308
+ y, type = k.to_s.split("__", 2)
309
+ prepared_args << [y.to_sym, type]
310
+ i = prepared_args.length
311
+ LiteralString.new(":#{i}")
312
+ end
313
+
314
+ # Always assume a prepared argument.
315
+ def prepared_arg?(k)
316
+ true
317
+ end
318
+ end
319
+
320
+ # Oracle prepared statement uses a new prepared statement each time
321
+ # it is called, but it does use the bind arguments.
322
+ module BindArgumentMethods
323
+ include ArgumentMapper
324
+
325
+ private
326
+
327
+ # Run execute_select on the database with the given SQL and the stored
328
+ # bind arguments.
329
+ def execute(sql, opts={}, &block)
330
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
331
+ end
332
+
333
+ # Same as execute, explicit due to intricacies of alias and super.
334
+ def execute_dui(sql, opts={}, &block)
335
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
336
+ end
337
+
338
+ # Same as execute, explicit due to intricacies of alias and super.
339
+ def execute_insert(sql, opts={}, &block)
340
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
341
+ end
342
+ end
343
+
344
+ module PreparedStatementMethods
345
+ include BindArgumentMethods
346
+
347
+ private
348
+
349
+ # Execute the stored prepared statement name and the stored bind
350
+ # arguments instead of the SQL given.
351
+ def execute(sql, opts={}, &block)
352
+ super(prepared_statement_name, opts, &block)
353
+ end
354
+
355
+ # Same as execute, explicit due to intricacies of alias and super.
356
+ def execute_dui(sql, opts={}, &block)
357
+ super(prepared_statement_name, opts, &block)
358
+ end
359
+
360
+ # Same as execute, explicit due to intricacies of alias and super.
361
+ def execute_insert(sql, opts={}, &block)
362
+ super(prepared_statement_name, opts, &block)
363
+ end
364
+ end
365
+
366
+ # Execute the given type of statement with the hash of values.
367
+ def call(type, bind_vars={}, *values, &block)
368
+ ps = to_prepared_statement(type, values)
369
+ ps.extend(BindArgumentMethods)
370
+ ps.call(bind_vars, &block)
371
+ end
372
+
105
373
  def fetch_rows(sql)
106
374
  execute(sql) do |cursor|
107
- begin
108
- @columns = cursor.get_col_names.map{|c| output_identifier(c)}
109
- while r = cursor.fetch
110
- row = {}
111
- r.each_with_index {|v, i| row[@columns[i]] = v unless @columns[i] == :raw_rnum_}
112
- yield row
113
- end
114
- ensure
115
- cursor.close
375
+ offset = @opts[:offset]
376
+ rn = row_number_column
377
+ cps = db.conversion_procs
378
+ cols = columns = cursor.get_col_names.map{|c| output_identifier(c)}
379
+ metadata = cursor.column_metadata
380
+ cm = cols.zip(metadata).map{|c, m| [c, cps[m.data_type]]}
381
+ columns = cols.reject{|x| x == rn} if offset
382
+ @columns = columns
383
+ while r = cursor.fetch
384
+ row = {}
385
+ r.zip(cm).each{|v, (c, cp)| row[c] = ((v && cp) ? cp.call(v) : v)}
386
+ row.delete(rn) if offset
387
+ yield row
116
388
  end
117
389
  end
118
390
  self
119
391
  end
120
392
 
393
+ # Prepare the given type of query with the given name and store
394
+ # it in the database. Note that a new native prepared statement is
395
+ # created on each call to this prepared statement.
396
+ def prepare(type, name=nil, *values)
397
+ ps = to_prepared_statement(type, values)
398
+ ps.extend(PreparedStatementMethods)
399
+ if name
400
+ ps.prepared_statement_name = name
401
+ db.set_prepared_statement(name, ps)
402
+ end
403
+ ps
404
+ end
405
+
406
+ # Oracle requires type specifiers for placeholders, at least
407
+ # if you ever want to use a nil/NULL value as the value for
408
+ # the placeholder.
409
+ def requires_placeholder_type_specifiers?
410
+ true
411
+ end
412
+
121
413
  private
122
414
 
123
- def literal_other(v)
415
+ def literal_other_append(sql, v)
124
416
  case v
125
417
  when OraDate
126
- literal(Sequel.database_to_application_timestamp(v))
418
+ literal_append(sql, db.to_application_timestamp(v))
127
419
  when OCI8::CLOB
128
420
  v.rewind
129
- literal(v.read)
421
+ literal_append(sql, v.read)
130
422
  else
131
423
  super
132
424
  end
133
425
  end
426
+
427
+ def prepared_arg_placeholder
428
+ PREPARED_ARG_PLACEHOLDER
429
+ end
134
430
  end
135
431
  end
136
432
  end