sequel 3.21.0 → 3.32.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 (234) hide show
  1. data/CHANGELOG +539 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -6
  4. data/Rakefile +37 -19
  5. data/bin/sequel +1 -5
  6. data/doc/advanced_associations.rdoc +17 -0
  7. data/doc/association_basics.rdoc +293 -43
  8. data/doc/dataset_basics.rdoc +4 -4
  9. data/doc/dataset_filtering.rdoc +8 -0
  10. data/doc/mass_assignment.rdoc +54 -0
  11. data/doc/migration.rdoc +15 -538
  12. data/doc/model_hooks.rdoc +72 -27
  13. data/doc/opening_databases.rdoc +89 -50
  14. data/doc/prepared_statements.rdoc +40 -13
  15. data/doc/reflection.rdoc +8 -2
  16. data/doc/release_notes/3.22.0.txt +39 -0
  17. data/doc/release_notes/3.23.0.txt +172 -0
  18. data/doc/release_notes/3.24.0.txt +420 -0
  19. data/doc/release_notes/3.25.0.txt +88 -0
  20. data/doc/release_notes/3.26.0.txt +88 -0
  21. data/doc/release_notes/3.27.0.txt +82 -0
  22. data/doc/release_notes/3.28.0.txt +304 -0
  23. data/doc/release_notes/3.29.0.txt +459 -0
  24. data/doc/release_notes/3.30.0.txt +135 -0
  25. data/doc/release_notes/3.31.0.txt +146 -0
  26. data/doc/release_notes/3.32.0.txt +202 -0
  27. data/doc/schema_modification.rdoc +547 -0
  28. data/doc/sharding.rdoc +7 -1
  29. data/doc/testing.rdoc +129 -0
  30. data/doc/transactions.rdoc +137 -0
  31. data/doc/virtual_rows.rdoc +2 -2
  32. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  33. data/lib/sequel/adapters/ado.rb +40 -18
  34. data/lib/sequel/adapters/amalgalite.rb +17 -9
  35. data/lib/sequel/adapters/db2.rb +175 -86
  36. data/lib/sequel/adapters/dbi.rb +15 -15
  37. data/lib/sequel/adapters/do/mysql.rb +5 -7
  38. data/lib/sequel/adapters/do/postgres.rb +0 -5
  39. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  40. data/lib/sequel/adapters/do.rb +17 -36
  41. data/lib/sequel/adapters/firebird.rb +27 -208
  42. data/lib/sequel/adapters/ibmdb.rb +448 -0
  43. data/lib/sequel/adapters/informix.rb +6 -23
  44. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  45. data/lib/sequel/adapters/jdbc/db2.rb +52 -0
  46. data/lib/sequel/adapters/jdbc/derby.rb +260 -0
  47. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  48. data/lib/sequel/adapters/jdbc/h2.rb +66 -30
  49. data/lib/sequel/adapters/jdbc/hsqldb.rb +177 -0
  50. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  51. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  52. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  53. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  54. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  56. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  57. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  58. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  59. data/lib/sequel/adapters/jdbc.rb +231 -78
  60. data/lib/sequel/adapters/mock.rb +339 -0
  61. data/lib/sequel/adapters/mysql.rb +85 -184
  62. data/lib/sequel/adapters/mysql2.rb +26 -25
  63. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  64. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  65. data/lib/sequel/adapters/odbc.rb +28 -9
  66. data/lib/sequel/adapters/openbase.rb +3 -5
  67. data/lib/sequel/adapters/oracle.rb +355 -53
  68. data/lib/sequel/adapters/postgres.rb +194 -33
  69. data/lib/sequel/adapters/shared/access.rb +32 -9
  70. data/lib/sequel/adapters/shared/db2.rb +328 -0
  71. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  72. data/lib/sequel/adapters/shared/informix.rb +53 -0
  73. data/lib/sequel/adapters/shared/mssql.rb +278 -107
  74. data/lib/sequel/adapters/shared/mysql.rb +203 -65
  75. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  76. data/lib/sequel/adapters/shared/oracle.rb +237 -46
  77. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  78. data/lib/sequel/adapters/shared/progress.rb +5 -8
  79. data/lib/sequel/adapters/shared/sqlite.rb +200 -50
  80. data/lib/sequel/adapters/sqlite.rb +29 -21
  81. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  82. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  83. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  84. data/lib/sequel/adapters/swift.rb +5 -5
  85. data/lib/sequel/adapters/tinytds.rb +160 -19
  86. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  87. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  88. data/lib/sequel/ast_transformer.rb +190 -0
  89. data/lib/sequel/connection_pool/threaded.rb +3 -2
  90. data/lib/sequel/connection_pool.rb +1 -1
  91. data/lib/sequel/core.rb +68 -5
  92. data/lib/sequel/database/connecting.rb +5 -6
  93. data/lib/sequel/database/dataset.rb +4 -4
  94. data/lib/sequel/database/dataset_defaults.rb +59 -1
  95. data/lib/sequel/database/logging.rb +1 -1
  96. data/lib/sequel/database/misc.rb +104 -19
  97. data/lib/sequel/database/query.rb +163 -59
  98. data/lib/sequel/database/schema_generator.rb +17 -7
  99. data/lib/sequel/database/schema_methods.rb +59 -29
  100. data/lib/sequel/dataset/actions.rb +240 -76
  101. data/lib/sequel/dataset/features.rb +91 -8
  102. data/lib/sequel/dataset/graph.rb +38 -11
  103. data/lib/sequel/dataset/misc.rb +50 -23
  104. data/lib/sequel/dataset/mutation.rb +12 -7
  105. data/lib/sequel/dataset/prepared_statements.rb +59 -27
  106. data/lib/sequel/dataset/query.rb +236 -43
  107. data/lib/sequel/dataset/sql.rb +676 -267
  108. data/lib/sequel/dataset.rb +8 -0
  109. data/lib/sequel/exceptions.rb +4 -0
  110. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  111. data/lib/sequel/extensions/migration.rb +6 -4
  112. data/lib/sequel/extensions/named_timezones.rb +5 -0
  113. data/lib/sequel/extensions/query.rb +1 -1
  114. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  115. data/lib/sequel/extensions/to_dot.rb +95 -83
  116. data/lib/sequel/model/associations.rb +1081 -344
  117. data/lib/sequel/model/base.rb +420 -133
  118. data/lib/sequel/model/errors.rb +1 -1
  119. data/lib/sequel/model/exceptions.rb +5 -1
  120. data/lib/sequel/model.rb +20 -10
  121. data/lib/sequel/plugins/active_model.rb +13 -1
  122. data/lib/sequel/plugins/association_pks.rb +22 -4
  123. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  124. data/lib/sequel/plugins/composition.rb +43 -10
  125. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  126. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  127. data/lib/sequel/plugins/force_encoding.rb +6 -6
  128. data/lib/sequel/plugins/identity_map.rb +114 -7
  129. data/lib/sequel/plugins/many_through_many.rb +68 -7
  130. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  131. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  132. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  133. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  134. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  135. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  136. data/lib/sequel/plugins/serialization.rb +98 -49
  137. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  138. data/lib/sequel/plugins/sharding.rb +21 -54
  139. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  140. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  141. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  142. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  143. data/lib/sequel/sql.rb +109 -52
  144. data/lib/sequel/timezones.rb +57 -38
  145. data/lib/sequel/version.rb +1 -1
  146. data/spec/adapters/db2_spec.rb +146 -0
  147. data/spec/adapters/mssql_spec.rb +135 -30
  148. data/spec/adapters/mysql_spec.rb +244 -212
  149. data/spec/adapters/oracle_spec.rb +67 -77
  150. data/spec/adapters/postgres_spec.rb +267 -78
  151. data/spec/adapters/spec_helper.rb +6 -5
  152. data/spec/adapters/sqlite_spec.rb +35 -21
  153. data/spec/core/connection_pool_spec.rb +71 -92
  154. data/spec/core/core_sql_spec.rb +21 -30
  155. data/spec/core/database_spec.rb +789 -512
  156. data/spec/core/dataset_spec.rb +1176 -1061
  157. data/spec/core/expression_filters_spec.rb +159 -42
  158. data/spec/core/mock_adapter_spec.rb +419 -0
  159. data/spec/core/object_graph_spec.rb +86 -114
  160. data/spec/core/schema_generator_spec.rb +3 -3
  161. data/spec/core/schema_spec.rb +298 -38
  162. data/spec/core/spec_helper.rb +6 -48
  163. data/spec/extensions/active_model_spec.rb +13 -0
  164. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  165. data/spec/extensions/association_dependencies_spec.rb +2 -12
  166. data/spec/extensions/association_pks_spec.rb +66 -32
  167. data/spec/extensions/caching_spec.rb +23 -50
  168. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  169. data/spec/extensions/columns_introspection_spec.rb +91 -0
  170. data/spec/extensions/composition_spec.rb +18 -13
  171. data/spec/extensions/dataset_associations_spec.rb +199 -0
  172. data/spec/extensions/defaults_setter_spec.rb +64 -0
  173. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  174. data/spec/extensions/identity_map_spec.rb +114 -22
  175. data/spec/extensions/instance_filters_spec.rb +11 -21
  176. data/spec/extensions/instance_hooks_spec.rb +53 -0
  177. data/spec/extensions/json_serializer_spec.rb +4 -5
  178. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  179. data/spec/extensions/list_spec.rb +17 -39
  180. data/spec/extensions/many_through_many_spec.rb +283 -250
  181. data/spec/extensions/migration_spec.rb +35 -32
  182. data/spec/extensions/named_timezones_spec.rb +23 -3
  183. data/spec/extensions/nested_attributes_spec.rb +97 -88
  184. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  185. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  186. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  187. data/spec/extensions/prepared_statements_spec.rb +53 -0
  188. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  189. data/spec/extensions/pretty_table_spec.rb +1 -6
  190. data/spec/extensions/query_spec.rb +6 -0
  191. data/spec/extensions/rcte_tree_spec.rb +41 -43
  192. data/spec/extensions/schema_dumper_spec.rb +5 -8
  193. data/spec/extensions/schema_spec.rb +13 -21
  194. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  195. data/spec/extensions/serialization_spec.rb +81 -43
  196. data/spec/extensions/sharding_spec.rb +66 -140
  197. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  198. data/spec/extensions/spec_helper.rb +13 -61
  199. data/spec/extensions/sql_expr_spec.rb +20 -60
  200. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  201. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  202. data/spec/extensions/timestamps_spec.rb +6 -6
  203. data/spec/extensions/to_dot_spec.rb +4 -7
  204. data/spec/extensions/touch_spec.rb +13 -14
  205. data/spec/extensions/tree_spec.rb +11 -26
  206. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  207. data/spec/extensions/update_primary_key_spec.rb +30 -24
  208. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  209. data/spec/extensions/validation_helpers_spec.rb +16 -35
  210. data/spec/extensions/xml_serializer_spec.rb +16 -4
  211. data/spec/integration/associations_test.rb +661 -21
  212. data/spec/integration/database_test.rb +4 -2
  213. data/spec/integration/dataset_test.rb +500 -67
  214. data/spec/integration/eager_loader_test.rb +19 -21
  215. data/spec/integration/model_test.rb +80 -1
  216. data/spec/integration/plugin_test.rb +367 -119
  217. data/spec/integration/prepared_statement_test.rb +222 -125
  218. data/spec/integration/schema_test.rb +217 -39
  219. data/spec/integration/spec_helper.rb +39 -30
  220. data/spec/integration/timezone_test.rb +38 -12
  221. data/spec/integration/transaction_test.rb +182 -5
  222. data/spec/integration/type_test.rb +17 -3
  223. data/spec/model/association_reflection_spec.rb +201 -7
  224. data/spec/model/associations_spec.rb +955 -662
  225. data/spec/model/base_spec.rb +275 -95
  226. data/spec/model/dataset_methods_spec.rb +7 -27
  227. data/spec/model/eager_loading_spec.rb +677 -676
  228. data/spec/model/hooks_spec.rb +349 -67
  229. data/spec/model/model_spec.rb +222 -168
  230. data/spec/model/plugins_spec.rb +24 -13
  231. data/spec/model/record_spec.rb +381 -218
  232. data/spec/model/spec_helper.rb +13 -71
  233. data/spec/model/validations_spec.rb +11 -0
  234. metadata +117 -73
@@ -0,0 +1,137 @@
1
+ = Database Transactions
2
+
3
+ Sequel uses autocommit mode by default for all of its database adapters, so in general in Sequel if you want to use database transactions, you need to be explicit about it. There are a few cases where transactions are used implicitly by default:
4
+
5
+ * Dataset#import to insert many records at once
6
+ * Model#save
7
+ * Model#destroy
8
+ * Migrations
9
+ * A few model plugins
10
+
11
+ Everywhere else, it is up to use to use a database transaction if you want to.
12
+
13
+ == Basic Transaction Usage
14
+
15
+ In Sequel, the <tt>Database#transaction</tt> method should be called if you want to use a database transaction. This method must be called with a block. If the block does not raise an exception, the transaction is committed:
16
+
17
+ DB.transaction do # BEGIN
18
+ DB[:foo].insert(1) # INSERT
19
+ end # COMMIT
20
+
21
+ If the block raises a Sequel::Rollback exception, the transaction is rolled back, but no exception is raised outside the block:
22
+
23
+ DB.transaction do # BEGIN
24
+ raise Sequel::Rollback
25
+ end # ROLLBACK
26
+ # no exception raised
27
+
28
+ If any other exception is raised, the transaction is rolled back, and the exception is raised outside the block:
29
+
30
+ # ArgumentError raised
31
+ DB.transaction do # BEGIN
32
+ raise ArgumentError
33
+ end # ROLLBACK
34
+ # ArgumentError raised
35
+
36
+ If you want Sequel::Rollback exceptions to be reraised, use the <tt>:rollback => :reraise</tt> option:
37
+
38
+ DB.transaction(:rollback => :reraise) do # BEGIN
39
+ raise Sequel::Rollback
40
+ end # ROLLBACK
41
+ # Sequel::Rollback raised
42
+
43
+ If you always want to rollback (useful for testing), use the <tt>:rollback => :always</tt> option:
44
+
45
+ DB.transaction(:rollback => :always) do # BEGIN
46
+ DB[:foo].insert(1) # INSERT
47
+ end # ROLLBACK
48
+
49
+ If you want to check whether you are currently in a transaction, use the Database#in_transaction? method:
50
+
51
+ DB.in_transaction? # false
52
+ DB.transaction do
53
+ DB.in_transaction? # true
54
+ end
55
+
56
+ == Transaction Hooks
57
+
58
+ You can add hooks to an in progress transaction that are called after the transaction commits or rolls back:
59
+
60
+ x = nil
61
+ DB.transaction do
62
+ DB.after_commit{x = 1}
63
+ DB.after_rollback{x = 2}
64
+ x # nil
65
+ end
66
+ x # 1
67
+
68
+ x = nil
69
+ DB.transaction do
70
+ DB.after_commit{x = 1}
71
+ DB.after_rollback{x = 2}
72
+ raise Sequel::Rollback
73
+ end
74
+ x # 2
75
+
76
+ == Nested Transaction Calls / Savepoints
77
+
78
+ You can nest calls to transaction, which by default just reuses the existing transaction:
79
+
80
+ DB.transaction do # BEGIN
81
+ DB.transaction do
82
+ DB[:foo].insert(1) # INSERT
83
+ end
84
+ end # COMMIT
85
+
86
+ You can use the <tt>:savepoint => true</tt> option in the inner transaction to explicitly use a savepoint (if the database supports it):
87
+
88
+ DB.transaction do # BEGIN
89
+ DB.transaction(:savepoint => true) do # SAVEPOINT
90
+ DB[:foo].insert(1) # INSERT
91
+ end # RELEASE SAVEPOINT
92
+ end # COMMIT
93
+
94
+ If a Sequel::Rollback exception is raised inside the savepoint block, it will only rollback to the savepoint:
95
+
96
+ DB.transaction do # BEGIN
97
+ DB.transaction(:savepoint => true) do # SAVEPOINT
98
+ raise Sequel::Rollback
99
+ end # ROLLBACK TO SAVEPOINT
100
+ # no exception raised
101
+ end # COMMIT
102
+
103
+ Other exceptions, unless rescued inside the outer transaction block, will rollback the savepoint and the outer transactions, since they are reraised by the transaction code:
104
+
105
+ DB.transaction do # BEGIN
106
+ DB.transaction(:savepoint => true) do # SAVEPOINT
107
+ raise ArgumentError
108
+ end # ROLLBACK TO SAVEPOINT
109
+ end # ROLLBACK
110
+ # ArgumentError raised
111
+
112
+ == Prepared Transactions / Two-Phase Commit
113
+
114
+ Sequel supports database prepared transactions on PostgreSQL, MySQL, and H2. With prepared transactions, at the end of the transaction, the transaction is not immediately committed (it acts like a rollback). Later, you can call +commit_prepared_transaction+ to commit the transaction or +rollback_prepared_transaction+ to roll the transaction back. Prepared transactions are usually used with distributed databases to make sure all databases commit the same transaction or none of them do.
115
+
116
+ To use prepared transactions in Sequel, you provide a string as the value of the :prepare option:
117
+
118
+ DB.transaction(:prepare => 'foo') do # BEGIN
119
+ DB[:foo].insert(1) # INSERT
120
+ end # PREPARE TRANSACTION 'foo'
121
+
122
+ Later, you can commit the prepared transaction:
123
+
124
+ DB.commit_prepared_transaction('foo')
125
+
126
+ or roll the prepared transaction back:
127
+
128
+ DB.rollback_prepared_transaction('foo')
129
+
130
+ == Transaction Isolation Levels
131
+
132
+ The SQL standard supports 4 isolation levels: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE. Not all databases implement the levels as specified in the standard (or implement the levels at all), but on PostgreSQL, MySQL, and Microsoft SQL Server, you can specify which transaction isolation level you want to use via the :isolation option to <tt>Database#transaction</tt>. The isolation level is specified as one of the following symbols: :uncommitted, :committed, :repeatable, and :serializable. Using this option make Sequel use the correct transaction isolation syntax for your database:
133
+
134
+ DB.transaction(:isolation => :serializable) do # BEGIN
135
+ # SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
136
+ DB[:foo].insert(1) # INSERT
137
+ end # COMMIT
@@ -40,7 +40,7 @@ available. For example, you are no longer able to do:
40
40
  # WHERE a AND (b OR NOT c)
41
41
 
42
42
  Because Symbol#&, Symbol#| and Symbol#~ are not defined when the core
43
- extensions are turned off. However, with virtual rows allow almost the same
43
+ extensions are turned off. However, virtual rows allow almost the same
44
44
  syntax even without the core extensions:
45
45
 
46
46
  dataset.filter{a & (b | ~c)}
@@ -87,7 +87,7 @@ and second is the difference between the the use of "a". In the regular proc,
87
87
  you couldn't call c without an explicit receiver in the proc, unless the self of the
88
88
  surrounding scope responded to it. For a, note how ruby calls the method on
89
89
  the receiver of the surrounding scope in the regular proc, which returns an integer,
90
- and does the substraction before Sequel gets access to it. In the instance evaled
90
+ and does the substitution before Sequel gets access to it. In the instance evaled
91
91
  proc, calling a without a receiver calls the a method on the VirtualRow instance.
92
92
  For b, note that it operates the same in both cases, as it is a local variable.
93
93
 
@@ -11,11 +11,6 @@ module Sequel
11
11
  # delete query.
12
12
  ROWS_AFFECTED = "SELECT @@ROWCOUNT AS AffectedRows"
13
13
 
14
- # Return instance of Sequel::ADO::MSSQL::Dataset with the given opts.
15
- def dataset(opts=nil)
16
- Sequel::ADO::MSSQL::Dataset.new(self, opts)
17
- end
18
-
19
14
  # Just execute so it doesn't attempt to return the number of rows modified.
20
15
  def execute_ddl(sql, opts={})
21
16
  execute(sql, opts)
@@ -37,11 +32,28 @@ module Sequel
37
32
  end
38
33
  end
39
34
  end
35
+
36
+ private
37
+
38
+ # The ADO adapter's default provider doesn't support transactions, since it
39
+ # creates a new native connection for each query. So Sequel only attempts
40
+ # to use transactions if an explicit :provider is given.
41
+ def begin_transaction(conn, opts={})
42
+ super if @opts[:provider]
43
+ end
44
+
45
+ def commit_transaction(conn, opts={})
46
+ super if @opts[:provider]
47
+ end
48
+
49
+ def rollback_transaction(conn, opts={})
50
+ super if @opts[:provider]
51
+ end
40
52
  end
41
53
 
42
54
  class Dataset < ADO::Dataset
43
55
  include Sequel::MSSQL::DatasetMethods
44
-
56
+
45
57
  # Use a nasty hack of multiple SQL statements in the same call and
46
58
  # having the last one return the most recently inserted id. This
47
59
  # is necessary as ADO's default :provider uses a separate native
@@ -4,6 +4,8 @@ module Sequel
4
4
  # The ADO adapter provides connectivity to ADO databases in Windows.
5
5
  module ADO
6
6
  class Database < Sequel::Database
7
+ DISCONNECT_ERROR_RE = /Communication link failure/
8
+
7
9
  set_adapter_scheme :ado
8
10
 
9
11
  def initialize(opts)
@@ -12,12 +14,15 @@ module Sequel
12
14
  when /Microsoft\.(Jet|ACE)\.OLEDB/io
13
15
  Sequel.ts_require 'adapters/shared/access'
14
16
  extend Sequel::Access::DatabaseMethods
15
- when nil
17
+ extend_datasets(Sequel::Access::DatasetMethods)
18
+ else
16
19
  @opts[:driver] ||= 'SQL Server'
17
20
  case @opts[:driver]
18
21
  when 'SQL Server'
19
22
  Sequel.ts_require 'adapters/ado/mssql'
20
23
  extend Sequel::ADO::MSSQL::DatabaseMethods
24
+ @dataset_class = ADO::MSSQL::Dataset
25
+ set_mssql_unicode_strings
21
26
  end
22
27
  end
23
28
  end
@@ -52,10 +57,6 @@ module Sequel
52
57
  handle
53
58
  end
54
59
 
55
- def dataset(opts = nil)
56
- ADO::Dataset.new(self, opts)
57
- end
58
-
59
60
  def execute(sql, opts={})
60
61
  synchronize(opts[:server]) do |conn|
61
62
  begin
@@ -74,28 +75,49 @@ module Sequel
74
75
  # The ADO adapter's default provider doesn't support transactions, since it
75
76
  # creates a new native connection for each query. So Sequel only attempts
76
77
  # to use transactions if an explicit :provider is given.
77
- def _transaction(conn, o={})
78
- return super if opts[:provider]
79
- th = Thread.current
80
- begin
81
- @transactions << th
82
- yield conn
83
- rescue Sequel::Rollback
84
- ensure
85
- @transactions.delete(th)
86
- end
78
+ def begin_transaction(conn, opts={})
79
+ super if @opts[:provider]
87
80
  end
88
-
81
+
82
+ def commit_transaction(conn, opts={})
83
+ super if @opts[:provider]
84
+ end
85
+
86
+ def database_error_classes
87
+ [::WIN32OLERuntimeError]
88
+ end
89
+
89
90
  def disconnect_connection(conn)
90
91
  conn.Close
91
92
  end
93
+
94
+ def disconnect_error?(e, opts)
95
+ super || (e.is_a?(::WIN32OLERuntimeError) && e.message =~ DISCONNECT_ERROR_RE)
96
+ end
97
+
98
+ def rollback_transaction(conn, opts={})
99
+ super if @opts[:provider]
100
+ end
92
101
  end
93
102
 
94
103
  class Dataset < Sequel::Dataset
104
+ Database::DatasetClass = self
105
+
95
106
  def fetch_rows(sql)
96
107
  execute(sql) do |s|
97
- @columns = cols = s.Fields.extend(Enumerable).map{|column| output_identifier(column.Name)}
98
- s.getRows.transpose.each{|r| yield cols.inject({}){|m,c| m[c] = r.shift; m}} unless s.eof
108
+ columns = cols = s.Fields.extend(Enumerable).map{|column| output_identifier(column.Name)}
109
+ if opts[:offset] && offset_returns_row_number_column?
110
+ rn = row_number_column
111
+ columns = columns.dup
112
+ columns.delete(rn)
113
+ end
114
+ @columns = columns
115
+ s.getRows.transpose.each do |r|
116
+ row = {}
117
+ cols.each{|c| row[c] = r.shift}
118
+ row.delete(rn) if rn
119
+ yield row
120
+ end unless s.eof
99
121
  end
100
122
  end
101
123
 
@@ -14,6 +14,12 @@ module Sequel
14
14
  'float' => ['float', 'double', 'real', 'double precision'],
15
15
  'decimal' => %w'numeric decimal money'
16
16
  )
17
+
18
+ # Store the related database object, in order to be able to correctly
19
+ # handle the database timezone.
20
+ def initialize(db)
21
+ @db = db
22
+ end
17
23
 
18
24
  # Return blobs as instances of Sequel::SQL::Blob instead of
19
25
  # Amalgamite::Blob
@@ -29,7 +35,11 @@ module Sequel
29
35
 
30
36
  # Return datetime types as instances of Sequel.datetime_class
31
37
  def datetime(s)
32
- Sequel.database_to_application_timestamp(s)
38
+ @db.to_application_timestamp(s)
39
+ end
40
+
41
+ def time(s)
42
+ Sequel.string_to_time(s)
33
43
  end
34
44
 
35
45
  # Don't raise an error if the value is a string and the declared
@@ -68,7 +78,8 @@ module Sequel
68
78
  opts[:database] = ':memory:' if blank_object?(opts[:database])
69
79
  db = ::Amalgalite::Database.new(opts[:database])
70
80
  db.busy_handler(::Amalgalite::BusyTimeout.new(opts.fetch(:timeout, 5000)/50, 50))
71
- db.type_map = SequelTypeMap.new
81
+ db.type_map = SequelTypeMap.new(self)
82
+ connection_pragmas.each{|s| log_yield(s){db.execute_batch(s)}}
72
83
  db
73
84
  end
74
85
 
@@ -77,11 +88,6 @@ module Sequel
77
88
  :sqlite
78
89
  end
79
90
 
80
- # Return instance of Sequel::Amalgalite::Dataset with the given options.
81
- def dataset(opts = nil)
82
- Amalgalite::Dataset.new(self, opts)
83
- end
84
-
85
91
  # Run the given SQL with the given arguments. Returns nil.
86
92
  def execute_ddl(sql, opts={})
87
93
  _execute(sql, opts){|conn| log_yield(sql){conn.execute_batch(sql)}}
@@ -151,6 +157,8 @@ module Sequel
151
157
  # Dataset class for SQLite datasets that use the amalgalite driver.
152
158
  class Dataset < Sequel::Dataset
153
159
  include ::Sequel::SQLite::DatasetMethods
160
+
161
+ Database::DatasetClass = self
154
162
 
155
163
  # Yield a hash for each row in the dataset.
156
164
  def fetch_rows(sql)
@@ -168,8 +176,8 @@ module Sequel
168
176
  private
169
177
 
170
178
  # Quote the string using the adapter instance method.
171
- def literal_string(v)
172
- db.synchronize{|c| c.quote(v)}
179
+ def literal_string_append(sql, v)
180
+ db.synchronize{|c| sql << c.quote(v)}
173
181
  end
174
182
  end
175
183
  end
@@ -1,139 +1,228 @@
1
1
  require 'db2/db2cli'
2
+ Sequel.require %w'shared/db2', 'adapters'
2
3
 
3
4
  module Sequel
4
5
  module DB2
6
+
7
+ @convert_smallint_to_bool = true
8
+
9
+ # Underlying error raised by Sequel, since ruby-db2 doesn't
10
+ # use exceptions.
11
+ class DB2Error < StandardError
12
+ end
13
+
14
+ class << self
15
+ # Whether to convert smallint values to bool, true by default.
16
+ # Can also be overridden per dataset.
17
+ attr_accessor :convert_smallint_to_bool
18
+ end
19
+
20
+ tt = Class.new do
21
+ def boolean(s) !s.to_i.zero? end
22
+ def date(s) Date.new(s.year, s.month, s.day) end
23
+ def time(s) Sequel::SQLTime.create(s.hour, s.minute, s.second) end
24
+ end.new
25
+
26
+ # Hash holding type translation methods, used by Dataset#fetch_rows.
27
+ DB2_TYPES = {
28
+ :boolean => tt.method(:boolean),
29
+ DB2CLI::SQL_BLOB => ::Sequel::SQL::Blob.method(:new),
30
+ DB2CLI::SQL_TYPE_DATE => tt.method(:date),
31
+ DB2CLI::SQL_TYPE_TIME => tt.method(:time),
32
+ DB2CLI::SQL_DECIMAL => ::BigDecimal.method(:new)
33
+ }
34
+ DB2_TYPES[DB2CLI::SQL_CLOB] = DB2_TYPES[DB2CLI::SQL_BLOB]
35
+
5
36
  class Database < Sequel::Database
6
- set_adapter_scheme :db2
37
+ include DatabaseMethods
7
38
 
8
- include DB2CLI
39
+ set_adapter_scheme :db2
9
40
 
10
41
  TEMPORARY = 'GLOBAL TEMPORARY '.freeze
42
+ rc, NullHandle = DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_ENV, DB2CLI::SQL_NULL_HANDLE)
11
43
 
12
- rc, @@env = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE)
13
- #check_error(rc, "Could not allocate DB2 environment")
44
+ # Hash of connection procs for converting
45
+ attr_reader :conversion_procs
46
+
47
+ def initialize(opts={})
48
+ super
49
+ @conversion_procs = DB2_TYPES.dup
50
+ @conversion_procs[DB2CLI::SQL_TYPE_TIMESTAMP] = method(:to_application_timestamp_db2)
51
+ end
14
52
 
15
53
  def connect(server)
16
54
  opts = server_opts(server)
17
- rc, dbc = SQLAllocHandle(SQL_HANDLE_DBC, @@env)
18
- check_error(rc, "Could not allocate database connection")
19
-
20
- rc = SQLConnect(dbc, opts[:database], opts[:user], opts[:password])
21
- check_error(rc, "Could not connect to database")
22
-
55
+ dbc = checked_error("Could not allocate database connection"){DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_DBC, NullHandle)}
56
+ checked_error("Could not connect to database"){DB2CLI.SQLConnect(dbc, opts[:database], opts[:user], opts[:password])}
23
57
  dbc
24
58
  end
25
59
 
26
- def test_connection(server=nil)
27
- synchronize(server){|conn|}
28
- true
60
+ def execute(sql, opts={}, &block)
61
+ synchronize(opts[:server]){|conn| log_connection_execute(conn, sql, &block)}
29
62
  end
63
+ alias do execute
30
64
 
31
- def dataset(opts = nil)
32
- DB2::Dataset.new(self, opts)
33
- end
34
-
35
- def execute(sql, opts={})
65
+ def execute_insert(sql, opts={})
36
66
  synchronize(opts[:server]) do |conn|
37
- rc, sth = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
38
- check_error(rc, "Could not allocate statement")
39
-
40
- begin
41
- rc = log_yield(sql){SQLExecDirect(sth, sql)}
42
- check_error(rc, "Could not execute statement")
43
-
44
- yield(sth) if block_given?
45
-
46
- rc, rpc = SQLRowCount(sth)
47
- check_error(rc, "Could not get RPC")
48
- rpc
49
- ensure
50
- rc = SQLFreeHandle(SQL_HANDLE_STMT, sth)
51
- check_error(rc, "Could not free statement")
67
+ log_connection_execute(conn, sql)
68
+ sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
69
+ log_connection_execute(conn, sql) do |sth|
70
+ name, buflen, datatype, size, digits, nullable = checked_error("Could not describe column"){DB2CLI.SQLDescribeCol(sth, 1, 256)}
71
+ if DB2CLI.SQLFetch(sth) != DB2CLI::SQL_NO_DATA_FOUND
72
+ v, _ = checked_error("Could not get data"){DB2CLI.SQLGetData(sth, 1, datatype, size)}
73
+ if v.is_a?(String)
74
+ return v.to_i
75
+ else
76
+ return nil
77
+ end
78
+ end
52
79
  end
53
80
  end
54
81
  end
55
- alias_method :do, :execute
56
-
57
- private
58
82
 
83
+ ERROR_MAP = {}
84
+ %w'SQL_INVALID_HANDLE SQL_STILL_EXECUTING SQL_ERROR'.each do |s|
85
+ ERROR_MAP[DB2CLI.const_get(s)] = s
86
+ end
59
87
  def check_error(rc, msg)
60
88
  case rc
61
- when SQL_SUCCESS, SQL_SUCCESS_WITH_INFO
89
+ when DB2CLI::SQL_SUCCESS, DB2CLI::SQL_SUCCESS_WITH_INFO, DB2CLI::SQL_NO_DATA_FOUND
62
90
  nil
91
+ when DB2CLI::SQL_INVALID_HANDLE, DB2CLI::SQL_STILL_EXECUTING
92
+ e = DB2Error.new("#{ERROR_MAP[rc]}: #{msg}")
93
+ e.set_backtrace(caller)
94
+ raise_error(e, :disconnect=>true)
63
95
  else
64
- raise DatabaseError, msg
96
+ e = DB2Error.new("#{ERROR_MAP[rc] || "Error code #{rc}"}: #{msg}")
97
+ e.set_backtrace(caller)
98
+ raise_error(e, :disconnect=>true)
65
99
  end
66
100
  end
67
101
 
68
- def disconnect_connection(conn)
69
- rc = SQLDisconnect(conn)
70
- check_error(rc, "Could not disconnect from database")
102
+ def checked_error(msg)
103
+ rc, *ary= yield
104
+ check_error(rc, msg)
105
+ ary.length <= 1 ? ary.first : ary
106
+ end
107
+
108
+ def to_application_timestamp_db2(v)
109
+ to_application_timestamp(v.to_s)
110
+ end
111
+
112
+ private
113
+
114
+ def begin_transaction(conn, opts={})
115
+ log_yield(TRANSACTION_BEGIN){DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_OFF)}
116
+ end
117
+
118
+ def remove_transaction(conn, committed)
119
+ DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_ON)
120
+ ensure
121
+ super
122
+ end
123
+
124
+ def rollback_transaction(conn, opts={})
125
+ log_yield(TRANSACTION_ROLLBACK){DB2CLI.SQLEndTran(DB2CLI::SQL_HANDLE_DBC, conn, DB2CLI::SQL_ROLLBACK)}
126
+ end
71
127
 
72
- rc = SQLFreeHandle(SQL_HANDLE_DBC, conn)
73
- check_error(rc, "Could not free Database handle")
128
+ def commit_transaction(conn, opts={})
129
+ log_yield(TRANSACTION_COMMIT){DB2CLI.SQLEndTran(DB2CLI::SQL_HANDLE_DBC, conn, DB2CLI::SQL_COMMIT)}
130
+ end
131
+
132
+ def log_connection_execute(conn, sql)
133
+ sth = checked_error("Could not allocate statement"){DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_STMT, conn)}
134
+
135
+ begin
136
+ checked_error("Could not execute statement: #{sql}"){log_yield(sql){DB2CLI.SQLExecDirect(sth, sql)}}
137
+
138
+ if block_given?
139
+ yield(sth)
140
+ else
141
+ checked_error("Could not get RPC"){DB2CLI.SQLRowCount(sth)}
142
+ end
143
+ ensure
144
+ checked_error("Could not free statement"){DB2CLI.SQLFreeHandle(DB2CLI::SQL_HANDLE_STMT, sth)}
145
+ end
146
+ end
147
+
148
+ # Convert smallint type to boolean if convert_smallint_to_bool is true
149
+ def schema_column_type(db_type)
150
+ if DB2.convert_smallint_to_bool && db_type =~ /smallint/i
151
+ :boolean
152
+ else
153
+ super
154
+ end
155
+ end
156
+
157
+ def disconnect_connection(conn)
158
+ checked_error("Could not disconnect from database"){DB2CLI.SQLDisconnect(conn)}
159
+ checked_error("Could not free Database handle"){DB2CLI.SQLFreeHandle(DB2CLI::SQL_HANDLE_DBC, conn)}
74
160
  end
75
161
  end
76
162
 
77
163
  class Dataset < Sequel::Dataset
164
+ include DatasetMethods
165
+
166
+ Database::DatasetClass = self
78
167
  MAX_COL_SIZE = 256
79
168
 
169
+ # Whether to convert smallint to boolean arguments for this dataset.
170
+ # Defaults to the DB2 module setting.
171
+ def convert_smallint_to_bool
172
+ defined?(@convert_smallint_to_bool) ? @convert_smallint_to_bool : (@convert_smallint_to_bool = DB2.convert_smallint_to_bool)
173
+ end
174
+
175
+ # Override the default DB2.convert_smallint_to_bool setting for this dataset.
176
+ attr_writer :convert_smallint_to_bool
177
+
80
178
  def fetch_rows(sql)
81
179
  execute(sql) do |sth|
82
- @column_info = get_column_info(sth)
83
- @columns = @column_info.map {|c| output_identifier(c[:name])}
84
- while (rc = SQLFetch(@handle)) != SQL_NO_DATA_FOUND
85
- @db.check_error(rc, "Could not fetch row")
86
- yield hash_row(sth)
180
+ offset = @opts[:offset]
181
+ db = @db
182
+ i = 1
183
+ column_info = get_column_info(sth)
184
+ cols = column_info.map{|c| c.at(1)}
185
+ cols.delete(row_number_column) if offset
186
+ @columns = cols
187
+ errors = [DB2CLI::SQL_NO_DATA_FOUND, DB2CLI::SQL_ERROR]
188
+ until errors.include?(rc = DB2CLI.SQLFetch(sth))
189
+ db.check_error(rc, "Could not fetch row")
190
+ row = {}
191
+ column_info.each do |i, c, t, s, pr|
192
+ v, _ = db.checked_error("Could not get data"){DB2CLI.SQLGetData(sth, i, t, s)}
193
+ row[c] = if v == DB2CLI::Null
194
+ nil
195
+ elsif pr
196
+ pr.call(v)
197
+ else
198
+ v
199
+ end
200
+ end
201
+ row.delete(row_number_column) if offset
202
+ yield row
87
203
  end
88
204
  end
89
205
  self
90
206
  end
91
207
 
92
- # DB2 supports window functions
93
- def supports_window_functions?
94
- true
95
- end
96
-
97
208
  private
98
209
 
99
210
  def get_column_info(sth)
100
- rc, column_count = SQLNumResultCols(sth)
101
- @db.check_error(rc, "Could not get number of result columns")
211
+ db = @db
212
+ column_count = db.checked_error("Could not get number of result columns"){DB2CLI.SQLNumResultCols(sth)}
213
+ convert = convert_smallint_to_bool
214
+ cps = db.conversion_procs
102
215
 
103
216
  (1..column_count).map do |i|
104
- rc, name, buflen, datatype, size, digits, nullable = SQLDescribeCol(sth, i, MAX_COL_SIZE)
105
- @b.check_error(rc, "Could not describe column")
106
-
107
- {:name => name, :db2_type => datatype, :precision => size}
217
+ name, buflen, datatype, size, digits, nullable = db.checked_error("Could not describe column"){DB2CLI.SQLDescribeCol(sth, i, MAX_COL_SIZE)}
218
+ pr = if datatype == DB2CLI::SQL_SMALLINT && convert && size <= 5 && digits <= 1
219
+ cps[:boolean]
220
+ else
221
+ cps[datatype]
222
+ end
223
+ [i, output_identifier(name), datatype, size, pr]
108
224
  end
109
225
  end
110
-
111
- def hash_row(sth)
112
- row = {}
113
- @column_info.each_with_index do |c, i|
114
- rc, v = SQLGetData(sth, i+1, c[:db2_type], c[:precision])
115
- @db.check_error(rc, "Could not get data")
116
-
117
- row[output_identifier(c[:name])] = convert_type(v)
118
- end
119
- row
120
- end
121
-
122
- def convert_type(v)
123
- case v
124
- when DB2CLI::Date
125
- DBI::Date.new(v.year, v.month, v.day)
126
- when DB2CLI::Time
127
- DBI::Time.new(v.hour, v.minute, v.second)
128
- when DB2CLI::Timestamp
129
- DBI::Timestamp.new(v.year, v.month, v.day,
130
- v.hour, v.minute, v.second, v.fraction)
131
- when DB2CLI::Null
132
- nil
133
- else
134
- v
135
- end
136
- end
137
226
  end
138
227
  end
139
228
  end