sequel 3.28.0 → 3.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. data/CHANGELOG +119 -3
  2. data/Rakefile +5 -3
  3. data/bin/sequel +1 -5
  4. data/doc/model_hooks.rdoc +9 -1
  5. data/doc/opening_databases.rdoc +49 -40
  6. data/doc/prepared_statements.rdoc +27 -6
  7. data/doc/release_notes/3.28.0.txt +2 -2
  8. data/doc/release_notes/3.29.0.txt +459 -0
  9. data/doc/sharding.rdoc +7 -1
  10. data/doc/testing.rdoc +18 -9
  11. data/doc/transactions.rdoc +41 -1
  12. data/lib/sequel/adapters/ado.rb +28 -17
  13. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  14. data/lib/sequel/adapters/amalgalite.rb +11 -7
  15. data/lib/sequel/adapters/db2.rb +122 -70
  16. data/lib/sequel/adapters/dbi.rb +15 -15
  17. data/lib/sequel/adapters/do.rb +5 -36
  18. data/lib/sequel/adapters/do/mysql.rb +0 -5
  19. data/lib/sequel/adapters/do/postgres.rb +0 -5
  20. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  21. data/lib/sequel/adapters/firebird.rb +3 -6
  22. data/lib/sequel/adapters/ibmdb.rb +24 -16
  23. data/lib/sequel/adapters/informix.rb +2 -4
  24. data/lib/sequel/adapters/jdbc.rb +47 -11
  25. data/lib/sequel/adapters/jdbc/as400.rb +5 -24
  26. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  27. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  28. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/h2.rb +10 -12
  30. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  31. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  32. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  33. data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
  34. data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
  35. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
  36. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  38. data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
  39. data/lib/sequel/adapters/mock.rb +315 -0
  40. data/lib/sequel/adapters/mysql.rb +64 -51
  41. data/lib/sequel/adapters/mysql2.rb +15 -9
  42. data/lib/sequel/adapters/odbc.rb +13 -6
  43. data/lib/sequel/adapters/odbc/db2.rb +0 -4
  44. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  45. data/lib/sequel/adapters/openbase.rb +2 -4
  46. data/lib/sequel/adapters/oracle.rb +333 -51
  47. data/lib/sequel/adapters/postgres.rb +80 -27
  48. data/lib/sequel/adapters/shared/access.rb +0 -6
  49. data/lib/sequel/adapters/shared/db2.rb +13 -15
  50. data/lib/sequel/adapters/shared/firebird.rb +6 -6
  51. data/lib/sequel/adapters/shared/mssql.rb +23 -18
  52. data/lib/sequel/adapters/shared/mysql.rb +6 -6
  53. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  54. data/lib/sequel/adapters/shared/oracle.rb +185 -30
  55. data/lib/sequel/adapters/shared/postgres.rb +35 -18
  56. data/lib/sequel/adapters/shared/progress.rb +0 -6
  57. data/lib/sequel/adapters/shared/sqlite.rb +116 -37
  58. data/lib/sequel/adapters/sqlite.rb +16 -8
  59. data/lib/sequel/adapters/swift.rb +5 -5
  60. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  61. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  62. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  63. data/lib/sequel/adapters/tinytds.rb +13 -10
  64. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
  65. data/lib/sequel/core.rb +40 -0
  66. data/lib/sequel/database/connecting.rb +1 -2
  67. data/lib/sequel/database/dataset.rb +3 -3
  68. data/lib/sequel/database/dataset_defaults.rb +58 -0
  69. data/lib/sequel/database/misc.rb +62 -2
  70. data/lib/sequel/database/query.rb +113 -49
  71. data/lib/sequel/database/schema_methods.rb +7 -2
  72. data/lib/sequel/dataset/actions.rb +37 -19
  73. data/lib/sequel/dataset/features.rb +24 -0
  74. data/lib/sequel/dataset/graph.rb +7 -6
  75. data/lib/sequel/dataset/misc.rb +11 -3
  76. data/lib/sequel/dataset/mutation.rb +2 -3
  77. data/lib/sequel/dataset/prepared_statements.rb +6 -4
  78. data/lib/sequel/dataset/query.rb +46 -15
  79. data/lib/sequel/dataset/sql.rb +28 -4
  80. data/lib/sequel/extensions/named_timezones.rb +5 -0
  81. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  82. data/lib/sequel/model.rb +2 -1
  83. data/lib/sequel/model/associations.rb +115 -33
  84. data/lib/sequel/model/base.rb +91 -31
  85. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  86. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  87. data/lib/sequel/plugins/force_encoding.rb +6 -6
  88. data/lib/sequel/plugins/identity_map.rb +1 -1
  89. data/lib/sequel/plugins/many_through_many.rb +6 -10
  90. data/lib/sequel/plugins/prepared_statements.rb +12 -1
  91. data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
  92. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  93. data/lib/sequel/plugins/serialization.rb +6 -1
  94. data/lib/sequel/plugins/sharding.rb +0 -5
  95. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  96. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  97. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  98. data/lib/sequel/timezones.rb +42 -42
  99. data/lib/sequel/version.rb +1 -1
  100. data/spec/adapters/mssql_spec.rb +29 -29
  101. data/spec/adapters/mysql_spec.rb +86 -104
  102. data/spec/adapters/oracle_spec.rb +48 -76
  103. data/spec/adapters/postgres_spec.rb +98 -33
  104. data/spec/adapters/spec_helper.rb +0 -5
  105. data/spec/adapters/sqlite_spec.rb +24 -21
  106. data/spec/core/connection_pool_spec.rb +9 -15
  107. data/spec/core/core_sql_spec.rb +20 -31
  108. data/spec/core/database_spec.rb +491 -227
  109. data/spec/core/dataset_spec.rb +638 -1051
  110. data/spec/core/expression_filters_spec.rb +0 -1
  111. data/spec/core/mock_adapter_spec.rb +378 -0
  112. data/spec/core/object_graph_spec.rb +48 -114
  113. data/spec/core/schema_generator_spec.rb +3 -3
  114. data/spec/core/schema_spec.rb +51 -114
  115. data/spec/core/spec_helper.rb +3 -90
  116. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  117. data/spec/extensions/dataset_associations_spec.rb +199 -0
  118. data/spec/extensions/instance_hooks_spec.rb +71 -0
  119. data/spec/extensions/named_timezones_spec.rb +22 -2
  120. data/spec/extensions/nested_attributes_spec.rb +3 -0
  121. data/spec/extensions/schema_spec.rb +1 -1
  122. data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
  123. data/spec/extensions/serialization_spec.rb +5 -8
  124. data/spec/extensions/spec_helper.rb +4 -0
  125. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  126. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  127. data/spec/integration/associations_test.rb +123 -12
  128. data/spec/integration/dataset_test.rb +140 -47
  129. data/spec/integration/eager_loader_test.rb +19 -21
  130. data/spec/integration/model_test.rb +80 -1
  131. data/spec/integration/plugin_test.rb +179 -128
  132. data/spec/integration/prepared_statement_test.rb +92 -91
  133. data/spec/integration/schema_test.rb +42 -23
  134. data/spec/integration/spec_helper.rb +25 -31
  135. data/spec/integration/timezone_test.rb +38 -12
  136. data/spec/integration/transaction_test.rb +161 -34
  137. data/spec/integration/type_test.rb +3 -3
  138. data/spec/model/association_reflection_spec.rb +83 -7
  139. data/spec/model/associations_spec.rb +393 -676
  140. data/spec/model/base_spec.rb +186 -116
  141. data/spec/model/dataset_methods_spec.rb +7 -27
  142. data/spec/model/eager_loading_spec.rb +343 -867
  143. data/spec/model/hooks_spec.rb +160 -79
  144. data/spec/model/model_spec.rb +118 -165
  145. data/spec/model/plugins_spec.rb +7 -13
  146. data/spec/model/record_spec.rb +138 -207
  147. data/spec/model/spec_helper.rb +10 -73
  148. metadata +14 -8
@@ -14,9 +14,6 @@ module Sequel
14
14
  def boolean(s) s.to_i != 0 end
15
15
  def integer(s) s.to_i end
16
16
  def float(s) s.to_f end
17
- def date(s) ::Sequel::MySQL.convert_date_time(:string_to_date, s) end
18
- def time(s) ::Sequel::MySQL.convert_date_time(:string_to_time, s) end
19
- def timestamp(s) ::Sequel::MySQL.convert_date_time(:database_to_application_timestamp, s) end
20
17
  end.new
21
18
 
22
19
  # Hash with integer keys and callable values for converting MySQL types.
@@ -30,52 +27,12 @@ module Sequel
30
27
  k.each{|n| MYSQL_TYPES[n] = v}
31
28
  end
32
29
 
33
- # Modify the type translator used for the tinyint type based
34
- # on the value given.
35
- def self.convert_tinyint_to_bool=(v)
36
- MYSQL_TYPES[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
37
- @convert_tinyint_to_bool = v
38
- end
39
- self.convert_tinyint_to_bool = convert_tinyint_to_bool
40
-
41
30
  class << self
42
- # By default, Sequel raises an exception if in invalid date or time is used.
43
- # However, if this is set to nil or :nil, the adapter treats dates
44
- # like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
45
- # it returns the strings as is.
46
- attr_reader :convert_invalid_date_time
47
- end
48
-
49
- # Modify the type translators for the date, time, and timestamp types
50
- # depending on the value given.
51
- def self.convert_invalid_date_time=(v)
52
- MYSQL_TYPES[11] = (v != false) ? TYPE_TRANSLATOR.method(:time) : ::Sequel.method(:string_to_time)
53
- m = (v != false) ? TYPE_TRANSLATOR.method(:date) : ::Sequel.method(:string_to_date)
54
- [10, 14].each{|i| MYSQL_TYPES[i] = m}
55
- m = (v != false) ? TYPE_TRANSLATOR.method(:timestamp) : ::Sequel.method(:database_to_application_timestamp)
56
- [7, 12].each{|i| MYSQL_TYPES[i] = m}
57
- @convert_invalid_date_time = v
31
+ # Whether to convert invalid date time values by default.
32
+ attr_accessor :convert_invalid_date_time
58
33
  end
59
34
  self.convert_invalid_date_time = false
60
35
 
61
- # If convert_invalid_date_time is nil, :nil, or :string and
62
- # the conversion raises an InvalidValue exception, return v
63
- # if :string and nil otherwise.
64
- def self.convert_date_time(meth, v)
65
- begin
66
- Sequel.send(meth, v)
67
- rescue InvalidValue
68
- case @convert_invalid_date_time
69
- when nil, :nil
70
- nil
71
- when :string
72
- v
73
- else
74
- raise
75
- end
76
- end
77
- end
78
-
79
36
  # Database class for MySQL databases used with Sequel.
80
37
  class Database < Sequel::Database
81
38
  include Sequel::MySQL::DatabaseMethods
@@ -85,7 +42,26 @@ module Sequel
85
42
  MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|Lost connection to MySQL server during query)/
86
43
 
87
44
  set_adapter_scheme :mysql
45
+
46
+ # Hash of conversion procs for the current database
47
+ attr_reader :conversion_procs
48
+ #
49
+ # Whether to convert tinyint columns to bool for the current database
50
+ attr_reader :convert_tinyint_to_bool
51
+
52
+ # By default, Sequel raises an exception if in invalid date or time is used.
53
+ # However, if this is set to nil or :nil, the adapter treats dates
54
+ # like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
55
+ # it returns the strings as is.
56
+ attr_reader :convert_invalid_date_time
88
57
 
58
+ def initialize(opts={})
59
+ super
60
+ @conversion_procs = MYSQL_TYPES.dup
61
+ self.convert_tinyint_to_bool = Sequel::MySQL.convert_tinyint_to_bool
62
+ self.convert_invalid_date_time = Sequel::MySQL.convert_invalid_date_time
63
+ end
64
+
89
65
  # Connect to the database. In addition to the usual database options,
90
66
  # the following options have effect:
91
67
  #
@@ -155,11 +131,27 @@ module Sequel
155
131
  conn
156
132
  end
157
133
 
158
- # Returns instance of Sequel::MySQL::Dataset with the given options.
159
- def dataset(opts = nil)
160
- MySQL::Dataset.new(self, opts)
134
+ # Modify the type translators for the date, time, and timestamp types
135
+ # depending on the value given.
136
+ def convert_invalid_date_time=(v)
137
+ m0 = ::Sequel.method(:string_to_time)
138
+ @conversion_procs[11] = (v != false) ? lambda{|v| convert_date_time(v, &m0)} : m0
139
+ m1 = ::Sequel.method(:string_to_date)
140
+ m = (v != false) ? lambda{|v| convert_date_time(v, &m1)} : m1
141
+ [10, 14].each{|i| @conversion_procs[i] = m}
142
+ m2 = method(:to_application_timestamp)
143
+ m = (v != false) ? lambda{|v| convert_date_time(v, &m2)} : m2
144
+ [7, 12].each{|i| @conversion_procs[i] = m}
145
+ @convert_invalid_date_time = v
161
146
  end
162
-
147
+
148
+ # Modify the type translator used for the tinyint type based
149
+ # on the value given.
150
+ def convert_tinyint_to_bool=(v)
151
+ @conversion_procs[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
152
+ @convert_tinyint_to_bool = v
153
+ end
154
+
163
155
  # Return the version of the MySQL server two which we are connecting.
164
156
  def server_version(server=nil)
165
157
  @server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
@@ -219,6 +211,24 @@ module Sequel
219
211
  :query
220
212
  end
221
213
 
214
+ # If convert_invalid_date_time is nil, :nil, or :string and
215
+ # the conversion raises an InvalidValue exception, return v
216
+ # if :string and nil otherwise.
217
+ def convert_date_time(v)
218
+ begin
219
+ yield v
220
+ rescue InvalidValue
221
+ case @convert_invalid_date_time
222
+ when nil, :nil
223
+ nil
224
+ when :string
225
+ v
226
+ else
227
+ raise
228
+ end
229
+ end
230
+ end
231
+
222
232
  # The MySQL adapter main error class is Mysql::Error
223
233
  def database_error_classes
224
234
  [Mysql::Error]
@@ -245,7 +255,7 @@ module Sequel
245
255
 
246
256
  # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
247
257
  def schema_column_type(db_type)
248
- Sequel::MySQL.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
258
+ convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
249
259
  end
250
260
  end
251
261
 
@@ -253,6 +263,8 @@ module Sequel
253
263
  class Dataset < Sequel::Dataset
254
264
  include Sequel::MySQL::DatasetMethods
255
265
  include Sequel::MySQL::PreparedStatements::DatasetMethods
266
+
267
+ Database::DatasetClass = self
256
268
 
257
269
  # Delete rows matching this dataset
258
270
  def delete
@@ -265,11 +277,12 @@ module Sequel
265
277
  def fetch_rows(sql, &block)
266
278
  execute(sql) do |r|
267
279
  i = -1
280
+ cps = db.conversion_procs
268
281
  cols = r.fetch_fields.map do |f|
269
282
  # Pretend tinyint is another integer type if its length is not 1, to
270
283
  # avoid casting to boolean if Sequel::MySQL.convert_tinyint_to_bool
271
284
  # is set.
272
- type_proc = f.type == 1 && f.length != 1 ? MYSQL_TYPES[2] : MYSQL_TYPES[f.type]
285
+ type_proc = f.type == 1 && f.length != 1 ? cps[2] : cps[f.type]
273
286
  [output_identifier(f.name), type_proc, i+=1]
274
287
  end
275
288
  @columns = cols.map{|c| c.first}
@@ -10,9 +10,18 @@ module Sequel
10
10
  include Sequel::MySQL::PreparedStatements::DatabaseMethods
11
11
 
12
12
  # Mysql::Error messages that indicate the current connection should be disconnected
13
- MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away)/
13
+ MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|This connection is still waiting for a result, try again once you have the result)/
14
14
 
15
15
  set_adapter_scheme :mysql2
16
+
17
+ # Whether to convert tinyint columns to bool for this database
18
+ attr_accessor :convert_tinyint_to_bool
19
+
20
+ # Set the convert_tinyint_to_bool setting based on the default value.
21
+ def initialize(opts={})
22
+ super
23
+ self.convert_tinyint_to_bool = Sequel::MySQL.convert_tinyint_to_bool
24
+ end
16
25
 
17
26
  # Connect to the database. In addition to the usual database options,
18
27
  # the following options have effect:
@@ -60,11 +69,6 @@ module Sequel
60
69
  conn
61
70
  end
62
71
 
63
- # Returns instance of Sequel::MySQL::Dataset with the given options.
64
- def dataset(opts = nil)
65
- Mysql2::Dataset.new(self, opts)
66
- end
67
-
68
72
  # Return the version of the MySQL server two which we are connecting.
69
73
  def server_version(server=nil)
70
74
  @server_version ||= (synchronize(server){|conn| conn.server_info[:id]} || super)
@@ -77,7 +81,7 @@ module Sequel
77
81
  # yield the connection if a block is given.
78
82
  def _execute(conn, sql, opts)
79
83
  begin
80
- r = log_yield(sql){conn.query(sql, :symbolize_keys => true, :database_timezone => Sequel.database_timezone, :application_timezone => Sequel.application_timezone)}
84
+ r = log_yield(sql){conn.query(sql, :symbolize_keys => true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
81
85
  if opts[:type] == :select
82
86
  yield r if r
83
87
  elsif block_given?
@@ -115,7 +119,7 @@ module Sequel
115
119
 
116
120
  # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
117
121
  def schema_column_type(db_type)
118
- Sequel::MySQL.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
122
+ convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
119
123
  end
120
124
  end
121
125
 
@@ -124,6 +128,8 @@ module Sequel
124
128
  include Sequel::MySQL::DatasetMethods
125
129
  include Sequel::MySQL::PreparedStatements::DatasetMethods
126
130
 
131
+ Database::DatasetClass = self
132
+
127
133
  # Delete rows matching this dataset
128
134
  def delete
129
135
  execute_dui(delete_sql){|c| return c.affected_rows}
@@ -133,7 +139,7 @@ module Sequel
133
139
  def fetch_rows(sql, &block)
134
140
  execute(sql) do |r|
135
141
  @columns = r.fields
136
- r.each(:cast_booleans => Sequel::MySQL.convert_tinyint_to_bool, &block)
142
+ r.each(:cast_booleans => db.convert_tinyint_to_bool, &block)
137
143
  end
138
144
  self
139
145
  end
@@ -15,13 +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
18
19
  set_mssql_unicode_strings
19
20
  when 'progress'
20
21
  Sequel.ts_require 'adapters/shared/progress'
21
22
  extend Sequel::Progress::DatabaseMethods
23
+ extend_datasets(Sequel::Progress::DatasetMethods)
22
24
  when 'db2'
23
25
  Sequel.ts_require 'adapters/odbc/db2'
24
26
  extend Sequel::ODBC::DB2::DatabaseMethods
27
+ @dataset_class = Sequel::ODBC::DB2::Dataset
25
28
  end
26
29
  end
27
30
 
@@ -45,10 +48,6 @@ module Sequel
45
48
  conn
46
49
  end
47
50
 
48
- def dataset(opts = nil)
49
- ODBC::Dataset.new(self, opts)
50
- end
51
-
52
51
  def execute(sql, opts={})
53
52
  synchronize(opts[:server]) do |conn|
54
53
  begin
@@ -99,15 +98,23 @@ module Sequel
99
98
  ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
100
99
  TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S'}".freeze
101
100
 
101
+ Database::DatasetClass = self
102
+
102
103
  def fetch_rows(sql)
103
104
  execute(sql) do |s|
104
105
  i = -1
105
106
  cols = s.columns(true).map{|c| [output_identifier(c.name), i+=1]}
106
- @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
107
113
  if rows = s.fetch_all
108
114
  rows.each do |row|
109
115
  hash = {}
110
116
  cols.each{|n,i| hash[n] = convert_odbc_value(row[i])}
117
+ hash.delete(rn) if rn
111
118
  yield hash
112
119
  end
113
120
  end
@@ -126,7 +133,7 @@ module Sequel
126
133
  # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
127
134
  case v
128
135
  when ::ODBC::TimeStamp
129
- 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])
130
137
  when ::ODBC::Time
131
138
  Sequel::SQLTime.create(v.hour, v.minute, v.second)
132
139
  when ::ODBC::Date
@@ -7,10 +7,6 @@ module Sequel
7
7
  module DB2
8
8
  module DatabaseMethods
9
9
  include ::Sequel::DB2::DatabaseMethods
10
-
11
- def dataset(opts=nil)
12
- Sequel::ODBC::DB2::Dataset.new(self, opts)
13
- end
14
10
  end
15
11
 
16
12
  class Dataset < ODBC::Dataset
@@ -9,11 +9,6 @@ module Sequel
9
9
  include Sequel::MSSQL::DatabaseMethods
10
10
  LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'
11
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
16
-
17
12
  # Return the last inserted identity value.
18
13
  def execute_insert(sql, opts={})
19
14
  synchronize(opts[:server]) do |conn|
@@ -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)}
@@ -37,6 +33,8 @@ module Sequel
37
33
 
38
34
  class Dataset < Sequel::Dataset
39
35
  SELECT_CLAUSE_METHODS = clause_methods(:select, %w'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,6 +34,7 @@ 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
27
40
 
@@ -36,59 +49,129 @@ module Sequel
36
49
  conn.exec("ALTER SESSION SET TIME_ZONE='-00:00'")
37
50
  end
38
51
 
52
+ class << conn
53
+ attr_reader :prepared_statements
54
+ end
55
+ conn.instance_variable_set(:@prepared_statements, {})
56
+
39
57
  conn
40
58
  end
41
-
42
- def dataset(opts = nil)
43
- Oracle::Dataset.new(self, opts)
59
+
60
+ def execute(sql, opts={}, &block)
61
+ _execute(nil, sql, opts, &block)
44
62
  end
63
+ alias do execute
45
64
 
46
- def schema_parse_table(table, opts={})
47
- ds = dataset
48
- ds.identifier_output_method = :downcase
49
- schema_and_table = "#{"#{quote_identifier(opts[:schema])}." if opts[:schema]}#{quote_identifier(table)}"
50
- table_schema = []
51
- metadata = transaction(opts){|conn| conn.describe_table(schema_and_table)}
52
- metadata.columns.each do |column|
53
- table_schema << [
54
- column.name.downcase.to_sym,
55
- {
56
- :type => column.data_type,
57
- :db_type => column.type_string.split(' ')[0],
58
- :type_string => column.type_string,
59
- :charset_form => column.charset_form,
60
- :char_used => column.char_used?,
61
- :char_size => column.char_size,
62
- :data_size => column.data_size,
63
- :precision => column.precision,
64
- :scale => column.scale,
65
- :fsprecision => column.fsprecision,
66
- :lfprecision => column.lfprecision,
67
- :allow_null => column.nullable?
68
- }
69
- ]
70
- end
71
- table_schema
65
+ def execute_insert(sql, opts={})
66
+ _execute(:insert, sql, opts)
72
67
  end
73
68
 
74
- def execute(sql, opts={})
69
+ private
70
+
71
+ def _execute(type, sql, opts={}, &block)
75
72
  synchronize(opts[:server]) do |conn|
76
73
  begin
77
- r = log_yield(sql){conn.exec(sql)}
78
- yield(r) if block_given?
79
- r
80
- rescue OCIException => e
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
81
96
  raise_error(e)
82
97
  end
83
98
  end
84
99
  end
85
- alias_method :do, :execute
86
100
 
87
- private
88
-
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
169
+ end
170
+ end
171
+ end
172
+
89
173
  def begin_transaction(conn, opts={})
90
174
  log_yield(TRANSACTION_BEGIN){conn.autocommit = false}
91
- conn
92
175
  end
93
176
 
94
177
  def commit_transaction(conn, opts={})
@@ -102,44 +185,239 @@ module Sequel
102
185
  end
103
186
 
104
187
  def disconnect_error?(e, opts)
105
- super || (e.is_a?(::OCIException) && CONNECTION_ERROR_CODES.include?(e.code))
188
+ super || (e.is_a?(::OCIError) && CONNECTION_ERROR_CODES.include?(e.code))
106
189
  end
107
190
 
108
- def remove_transaction(conn)
109
- 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
110
212
  super
111
213
  end
112
214
 
113
215
  def rollback_transaction(conn, opts={})
114
216
  log_yield(TRANSACTION_ROLLBACK){conn.rollback}
115
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
116
272
  end
117
273
 
118
274
  class Dataset < Sequel::Dataset
119
275
  include DatasetMethods
120
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
+
121
376
  def fetch_rows(sql)
122
377
  execute(sql) do |cursor|
123
- begin
124
- @columns = cursor.get_col_names.map{|c| output_identifier(c)}
125
- while r = cursor.fetch
126
- row = {}
127
- r.each_with_index {|v, i| row[@columns[i]] = v unless @columns[i] == :raw_rnum_}
128
- yield row
129
- end
130
- ensure
131
- 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
132
391
  end
133
392
  end
134
393
  self
135
394
  end
136
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
+
137
415
  private
138
416
 
139
417
  def literal_other(v)
140
418
  case v
141
419
  when OraDate
142
- literal(Sequel.database_to_application_timestamp(v))
420
+ literal(db.to_application_timestamp(v))
143
421
  when OCI8::CLOB
144
422
  v.rewind
145
423
  literal(v.read)
@@ -147,6 +425,10 @@ module Sequel
147
425
  super
148
426
  end
149
427
  end
428
+
429
+ def prepared_arg_placeholder
430
+ PREPARED_ARG_PLACEHOLDER
431
+ end
150
432
  end
151
433
  end
152
434
  end