sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -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,144 @@ 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_statements[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("Preparing #{name}: #{sql}"){conn.parse(sql)}
140
+ conn.prepared_statements[name] = [cursor, sql]
141
+ end
142
+ args = cursor_bind_params(conn, cursor, opts[:arguments])
143
+ r = log_yield("Executing #{name}", args){cursor.exec}
144
+ if block_given?
145
+ yield(cursor)
146
+ elsif type == :insert
147
+ last_insert_id(conn, opts)
148
+ else
149
+ r
150
+ end
151
+ end
152
+
153
+ def last_insert_id(conn, opts)
154
+ unless sequence = opts[:sequence]
155
+ if t = opts[:table]
156
+ sequence = sequence_for_table(t)
157
+ end
158
+ end
159
+ if sequence
160
+ sql = "SELECT #{literal(sequence)}.currval FROM dual"
161
+ begin
162
+ cursor = log_yield(sql){conn.exec(sql)}
163
+ row = cursor.fetch
164
+ row.each{|v| return (v.to_i if v)}
165
+ rescue OCIError
166
+ nil
167
+ ensure
168
+ cursor.close if cursor
70
169
  end
71
170
  end
72
171
  end
73
- alias_method :do, :execute
74
172
 
75
- private
76
-
77
173
  def begin_transaction(conn, opts={})
78
174
  log_yield(TRANSACTION_BEGIN){conn.autocommit = false}
79
- conn
80
175
  end
81
176
 
82
177
  def commit_transaction(conn, opts={})
@@ -88,42 +183,241 @@ module Sequel
88
183
  rescue OCIInvalidHandle
89
184
  nil
90
185
  end
186
+
187
+ def disconnect_error?(e, opts)
188
+ super || (e.is_a?(::OCIError) && CONNECTION_ERROR_CODES.include?(e.code))
189
+ end
91
190
 
92
- def remove_transaction(conn)
93
- conn.autocommit = true if conn
191
+ def oracle_column_type(h)
192
+ case h[:oci8_type]
193
+ when :number
194
+ case h[:scale]
195
+ when 0
196
+ :integer
197
+ when -127
198
+ :float
199
+ else
200
+ :decimal
201
+ end
202
+ when :date
203
+ :datetime
204
+ else
205
+ schema_column_type(h[:db_type])
206
+ end
207
+ end
208
+
209
+ def remove_transaction(conn, committed)
210
+ conn.autocommit = true
211
+ ensure
94
212
  super
95
213
  end
96
214
 
97
215
  def rollback_transaction(conn, opts={})
98
216
  log_yield(TRANSACTION_ROLLBACK){conn.rollback}
99
217
  end
218
+
219
+ def schema_parse_table(table, opts={})
220
+ schema, table = schema_and_table(table)
221
+ schema ||= opts[:schema]
222
+ schema_and_table = if ds = opts[:dataset]
223
+ ds.literal(schema ? SQL::QualifiedIdentifier.new(schema, table) : SQL::Identifier.new(table))
224
+ else
225
+ "#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
226
+ end
227
+ table_schema = []
228
+ m = output_identifier_meth(ds)
229
+ im = input_identifier_meth(ds)
230
+
231
+ # Primary Keys
232
+ ds = metadata_dataset.from(:all_constraints___cons, :all_cons_columns___cols).
233
+ where(:cols__table_name=>im.call(table), :cons__constraint_type=>'P',
234
+ :cons__constraint_name=>:cols__constraint_name, :cons__owner=>:cols__owner)
235
+ ds = ds.where(:cons__owner=>im.call(schema)) if schema
236
+ pks = ds.select_map(:cols__column_name)
237
+
238
+ # Default values
239
+ defaults = metadata_dataset.from(:dba_tab_cols).
240
+ where(:table_name=>im.call(table)).
241
+ to_hash(:column_name, :data_default)
242
+
243
+ metadata = synchronize(opts[:server]) do |conn|
244
+ begin
245
+ log_yield("Connection.describe_table"){conn.describe_table(schema_and_table)}
246
+ rescue OCIError => e
247
+ raise_error(e)
248
+ end
249
+ end
250
+ metadata.columns.each do |column|
251
+ h = {
252
+ :primary_key => pks.include?(column.name),
253
+ :default => defaults[column.name],
254
+ :oci8_type => column.data_type,
255
+ :db_type => column.type_string.split(' ')[0],
256
+ :type_string => column.type_string,
257
+ :charset_form => column.charset_form,
258
+ :char_used => column.char_used?,
259
+ :char_size => column.char_size,
260
+ :data_size => column.data_size,
261
+ :precision => column.precision,
262
+ :scale => column.scale,
263
+ :fsprecision => column.fsprecision,
264
+ :lfprecision => column.lfprecision,
265
+ :allow_null => column.nullable?
266
+ }
267
+ h[:type] = oracle_column_type(h)
268
+ table_schema << [m.call(column.name), h]
269
+ end
270
+ table_schema
271
+ end
100
272
  end
101
273
 
102
274
  class Dataset < Sequel::Dataset
103
275
  include DatasetMethods
104
276
 
277
+ Database::DatasetClass = self
278
+
279
+ PREPARED_ARG_PLACEHOLDER = ':'.freeze
280
+
281
+ # Oracle already supports named bind arguments, so use directly.
282
+ module ArgumentMapper
283
+ include Sequel::Dataset::ArgumentMapper
284
+
285
+ protected
286
+
287
+ # Return a hash with the same values as the given hash,
288
+ # but with the keys converted to strings.
289
+ def map_to_prepared_args(bind_vars)
290
+ prepared_args.map{|v, t| [bind_vars[v], t]}
291
+ end
292
+
293
+ private
294
+
295
+ # Oracle uses a : before the name of the argument for named
296
+ # arguments.
297
+ def prepared_arg(k)
298
+ y, type = k.to_s.split("__", 2)
299
+ prepared_args << [y.to_sym, type]
300
+ i = prepared_args.length
301
+ LiteralString.new(":#{i}")
302
+ end
303
+
304
+ # Always assume a prepared argument.
305
+ def prepared_arg?(k)
306
+ true
307
+ end
308
+ end
309
+
310
+ # Oracle prepared statement uses a new prepared statement each time
311
+ # it is called, but it does use the bind arguments.
312
+ module BindArgumentMethods
313
+ include ArgumentMapper
314
+
315
+ private
316
+
317
+ # Run execute_select on the database with the given SQL and the stored
318
+ # bind arguments.
319
+ def execute(sql, opts={}, &block)
320
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
321
+ end
322
+
323
+ # Same as execute, explicit due to intricacies of alias and super.
324
+ def execute_dui(sql, opts={}, &block)
325
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
326
+ end
327
+
328
+ # Same as execute, explicit due to intricacies of alias and super.
329
+ def execute_insert(sql, opts={}, &block)
330
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
331
+ end
332
+ end
333
+
334
+ module PreparedStatementMethods
335
+ include BindArgumentMethods
336
+
337
+ private
338
+
339
+ # Execute the stored prepared statement name and the stored bind
340
+ # arguments instead of the SQL given.
341
+ def execute(sql, opts={}, &block)
342
+ super(prepared_statement_name, opts, &block)
343
+ end
344
+
345
+ # Same as execute, explicit due to intricacies of alias and super.
346
+ def execute_dui(sql, opts={}, &block)
347
+ super(prepared_statement_name, opts, &block)
348
+ end
349
+
350
+ # Same as execute, explicit due to intricacies of alias and super.
351
+ def execute_insert(sql, opts={}, &block)
352
+ super(prepared_statement_name, opts, &block)
353
+ end
354
+ end
355
+
356
+ # Execute the given type of statement with the hash of values.
357
+ def call(type, bind_vars={}, *values, &block)
358
+ ps = to_prepared_statement(type, values)
359
+ ps.extend(BindArgumentMethods)
360
+ ps.call(bind_vars, &block)
361
+ end
362
+
363
+ # Prepare the given type of query with the given name and store
364
+ # it in the database. Note that a new native prepared statement is
365
+ # created on each call to this prepared statement.
366
+ def prepare(type, name=nil, *values)
367
+ ps = to_prepared_statement(type, values)
368
+ ps.extend(PreparedStatementMethods)
369
+ if name
370
+ ps.prepared_statement_name = name
371
+ db.prepared_statements[name] = ps
372
+ end
373
+ ps
374
+ end
375
+
105
376
  def fetch_rows(sql)
106
377
  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
378
+ offset = @opts[:offset]
379
+ rn = row_number_column
380
+ cps = db.conversion_procs
381
+ cols = columns = cursor.get_col_names.map{|c| output_identifier(c)}
382
+ metadata = cursor.column_metadata
383
+ cm = cols.zip(metadata).map{|c, m| [c, cps[m.data_type]]}
384
+ columns = cols.reject{|x| x == rn} if offset
385
+ @columns = columns
386
+ while r = cursor.fetch
387
+ row = {}
388
+ r.zip(cm).each{|v, (c, cp)| row[c] = ((v && cp) ? cp.call(v) : v)}
389
+ row.delete(rn) if offset
390
+ yield row
116
391
  end
117
392
  end
118
393
  self
119
394
  end
120
395
 
396
+ # Create a named prepared statement that is stored in the
397
+ # database (and connection) for reuse.
398
+ def prepare(type, name=nil, *values)
399
+ ps = to_prepared_statement(type, values)
400
+ ps.extend(PreparedStatementMethods)
401
+ if name
402
+ ps.prepared_statement_name = name
403
+ db.prepared_statements[name] = ps
404
+ end
405
+ ps
406
+ end
407
+
408
+ # Oracle requires type specifiers for placeholders, at least
409
+ # if you ever want to use a nil/NULL value as the value for
410
+ # the placeholder.
411
+ def requires_placeholder_type_specifiers?
412
+ true
413
+ end
414
+
121
415
  private
122
416
 
123
417
  def literal_other(v)
124
418
  case v
125
419
  when OraDate
126
- literal(Sequel.database_to_application_timestamp(v))
420
+ literal(db.to_application_timestamp(v))
127
421
  when OCI8::CLOB
128
422
  v.rewind
129
423
  literal(v.read)
@@ -131,6 +425,10 @@ module Sequel
131
425
  super
132
426
  end
133
427
  end
428
+
429
+ def prepared_arg_placeholder
430
+ PREPARED_ARG_PLACEHOLDER
431
+ end
134
432
  end
135
433
  end
136
434
  end