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
@@ -83,13 +83,9 @@ module Sequel
83
83
  @server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
84
84
  end
85
85
 
86
- # Return an array of symbols specifying table names in the current database.
87
- #
88
- # Options:
89
- # * :server - Set the server to use
90
- def tables(opts={})
91
- m = output_identifier_meth
92
- metadata_dataset.with_sql('SHOW TABLES').server(opts[:server]).map{|r| m.call(r.values.first)}
86
+ # MySQL supports CREATE TABLE IF NOT EXISTS syntax.
87
+ def supports_create_table_if_not_exists?
88
+ true
93
89
  end
94
90
 
95
91
  # MySQL supports prepared transactions (two-phase commit) using XA
@@ -107,6 +103,14 @@ module Sequel
107
103
  true
108
104
  end
109
105
 
106
+ # Return an array of symbols specifying table names in the current database.
107
+ #
108
+ # Options:
109
+ # * :server - Set the server to use
110
+ def tables(opts={})
111
+ full_tables('BASE TABLE', opts)
112
+ end
113
+
110
114
  # Changes the database in use by issuing a USE statement. I would be
111
115
  # very careful if I used this.
112
116
  def use(db_name)
@@ -116,6 +120,14 @@ module Sequel
116
120
  self
117
121
  end
118
122
 
123
+ # Return an array of symbols specifying view names in the current database.
124
+ #
125
+ # Options:
126
+ # * :server - Set the server to use
127
+ def views(opts={})
128
+ full_tables('VIEW', opts)
129
+ end
130
+
119
131
  private
120
132
 
121
133
  # Use MySQL specific syntax for rename column, set column type, and
@@ -142,6 +154,18 @@ module Sequel
142
154
  "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(opts))}"
143
155
  when :drop_index
144
156
  "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
157
+ when :drop_constraint
158
+ type = case op[:type]
159
+ when :primary_key
160
+ return "ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY"
161
+ when :foreign_key
162
+ 'FOREIGN KEY'
163
+ when :unique
164
+ 'INDEX'
165
+ else
166
+ raise(Error, "must specify constraint type via :type=>(:foreign_key|:primary_key|:unique) when dropping constraints on MySQL")
167
+ end
168
+ "ALTER TABLE #{quote_schema_table(table)} DROP #{type} #{quote_identifier(op[:name])}"
145
169
  else
146
170
  super(table, op)
147
171
  end
@@ -161,9 +185,9 @@ module Sequel
161
185
  # Use XA START to start a new prepared transaction if the :prepare
162
186
  # option is given.
163
187
  def begin_transaction(conn, opts={})
164
- if s = opts[:prepare]
188
+ if (s = opts[:prepare]) && (th = @transactions[conn])[:savepoint_level] == 0
165
189
  log_connection_execute(conn, "XA START #{literal(s)}")
166
- conn
190
+ th[:savepoint_level] += 1
167
191
  else
168
192
  super
169
193
  end
@@ -184,7 +208,7 @@ module Sequel
184
208
  # Prepare the XA transaction for a two-phase commit if the
185
209
  # :prepare option is given.
186
210
  def commit_transaction(conn, opts={})
187
- if s = opts[:prepare]
211
+ if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
188
212
  log_connection_execute(conn, "XA END #{literal(s)}")
189
213
  log_connection_execute(conn, "XA PREPARE #{literal(s)}")
190
214
  else
@@ -205,6 +229,12 @@ module Sequel
205
229
  "#{super}#{" ENGINE=#{engine}" if engine}#{" DEFAULT CHARSET=#{charset}" if charset}#{" DEFAULT COLLATE=#{collate}" if collate}"
206
230
  end
207
231
 
232
+ # Backbone of the tables and views support using SHOW FULL TABLES.
233
+ def full_tables(type, opts)
234
+ m = output_identifier_meth
235
+ metadata_dataset.with_sql('SHOW FULL TABLES').server(opts[:server]).map{|r| m.call(r.values.first) if r.delete(:Table_type) == type}.compact
236
+ end
237
+
208
238
  # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
209
239
  def identifier_input_method_default
210
240
  nil
@@ -232,7 +262,7 @@ module Sequel
232
262
 
233
263
  # Rollback the currently open XA transaction
234
264
  def rollback_transaction(conn, opts={})
235
- if s = opts[:prepare]
265
+ if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
236
266
  log_connection_execute(conn, "XA END #{literal(s)}")
237
267
  log_connection_execute(conn, "XA PREPARE #{literal(s)}")
238
268
  log_connection_execute(conn, "XA ROLLBACK #{literal(s)}")
@@ -248,8 +278,8 @@ module Sequel
248
278
 
249
279
  # Use the MySQL specific DESCRIBE syntax to get a table description.
250
280
  def schema_parse_table(table_name, opts)
251
- m = output_identifier_meth
252
- im = input_identifier_meth
281
+ m = output_identifier_meth(opts[:dataset])
282
+ im = input_identifier_meth(opts[:dataset])
253
283
  metadata_dataset.with_sql("DESCRIBE ?", SQL::Identifier.new(im.call(table_name))).map do |row|
254
284
  row[:auto_increment] = true if row.delete(:Extra).to_s =~ /auto_increment/io
255
285
  row[:allow_null] = row.delete(:Null) == 'YES'
@@ -312,6 +342,13 @@ module Sequel
312
342
  # string concatenation.
313
343
  def complex_expression_sql(op, args)
314
344
  case op
345
+ when :IN, :"NOT IN"
346
+ ds = args.at(1)
347
+ if ds.is_a?(Sequel::Dataset) && ds.opts[:limit]
348
+ super(op, [args.at(0), ds.from_self])
349
+ else
350
+ super
351
+ end
315
352
  when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
316
353
  "(#{literal(args.at(0))} #{'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)}#{[:~, :'!~', :'~*', :'!~*'].include?(op) ? 'REGEXP' : 'LIKE'} #{'BINARY ' if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)}#{literal(args.at(1))})"
317
354
  when :'||'
@@ -451,6 +488,12 @@ module Sequel
451
488
  true
452
489
  end
453
490
 
491
+ # MySQL's DISTINCT ON emulation using GROUP BY does not respect the
492
+ # queries ORDER BY clause.
493
+ def supports_ordered_distinct_on?
494
+ false
495
+ end
496
+
454
497
  # MySQL does support fractional timestamps in literal timestamps, but it
455
498
  # ignores them. Also, using them seems to cause problems on 1.9. Since
456
499
  # they are ignored anyway, not using them is probably best.
@@ -0,0 +1,149 @@
1
+ Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
2
+
3
+ module Sequel
4
+ module MySQL
5
+ # This module is used by the mysql and mysql2 adapters to support
6
+ # prepared statements and stored procedures.
7
+ module PreparedStatements
8
+ module DatabaseMethods
9
+ # Support stored procedures on MySQL
10
+ def call_sproc(name, opts={}, &block)
11
+ args = opts[:args] || []
12
+ execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
13
+ end
14
+
15
+ # Executes the given SQL using an available connection, yielding the
16
+ # connection if the block is given.
17
+ def execute(sql, opts={}, &block)
18
+ if opts[:sproc]
19
+ call_sproc(sql, opts, &block)
20
+ elsif sql.is_a?(Symbol)
21
+ execute_prepared_statement(sql, opts, &block)
22
+ else
23
+ synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def add_prepared_statements_cache(conn)
30
+ class << conn
31
+ attr_accessor :prepared_statements
32
+ end
33
+ conn.prepared_statements = {}
34
+ end
35
+
36
+ # Executes a prepared statement on an available connection. If the
37
+ # prepared statement already exists for the connection and has the same
38
+ # SQL, reuse it, otherwise, prepare the new statement. Because of the
39
+ # usual MySQL stupidity, we are forced to name arguments via separate
40
+ # SET queries. Use @sequel_arg_N (for N starting at 1) for these
41
+ # arguments.
42
+ def execute_prepared_statement(ps_name, opts, &block)
43
+ args = opts[:arguments]
44
+ ps = prepared_statements[ps_name]
45
+ sql = ps.prepared_sql
46
+ synchronize(opts[:server]) do |conn|
47
+ unless conn.prepared_statements[ps_name] == sql
48
+ conn.prepared_statements[ps_name] = sql
49
+ _execute(conn, "PREPARE #{ps_name} FROM #{literal(sql)}", opts)
50
+ end
51
+ i = 0
52
+ _execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
53
+ _execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
54
+ end
55
+ end
56
+
57
+ end
58
+ module DatasetMethods
59
+ include Sequel::Dataset::StoredProcedures
60
+
61
+ # Methods to add to MySQL prepared statement calls without using a
62
+ # real database prepared statement and bound variables.
63
+ module CallableStatementMethods
64
+ # Extend given dataset with this module so subselects inside subselects in
65
+ # prepared statements work.
66
+ def subselect_sql(ds)
67
+ ps = ds.to_prepared_statement(:select)
68
+ ps.extend(CallableStatementMethods)
69
+ ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
70
+ ps.prepared_args = prepared_args
71
+ ps.prepared_sql
72
+ end
73
+ end
74
+
75
+ # Methods for MySQL prepared statements using the native driver.
76
+ module PreparedStatementMethods
77
+ include Sequel::Dataset::UnnumberedArgumentMapper
78
+
79
+ # Raise a more obvious error if you attempt to call a unnamed prepared statement.
80
+ def call(*)
81
+ raise Error, "Cannot call prepared statement without a name" if prepared_statement_name.nil?
82
+ super
83
+ end
84
+
85
+ private
86
+
87
+ # Execute the prepared statement with the bind arguments instead of
88
+ # the given SQL.
89
+ def execute(sql, opts={}, &block)
90
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
91
+ end
92
+
93
+ # Same as execute, explicit due to intricacies of alias and super.
94
+ def execute_dui(sql, opts={}, &block)
95
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
96
+ end
97
+ end
98
+
99
+ # Methods for MySQL stored procedures using the native driver.
100
+ module StoredProcedureMethods
101
+ include Sequel::Dataset::StoredProcedureMethods
102
+
103
+ private
104
+
105
+ # Execute the database stored procedure with the stored arguments.
106
+ def execute(sql, opts={}, &block)
107
+ super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
108
+ end
109
+
110
+ # Same as execute, explicit due to intricacies of alias and super.
111
+ def execute_dui(sql, opts={}, &block)
112
+ super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
113
+ end
114
+ end
115
+
116
+ # MySQL is different in that it supports prepared statements but not bound
117
+ # variables outside of prepared statements. The default implementation
118
+ # breaks the use of subselects in prepared statements, so extend the
119
+ # temporary prepared statement that this creates with a module that
120
+ # fixes it.
121
+ def call(type, bind_arguments={}, *values, &block)
122
+ ps = to_prepared_statement(type, values)
123
+ ps.extend(CallableStatementMethods)
124
+ ps.call(bind_arguments, &block)
125
+ end
126
+
127
+ # Store the given type of prepared statement in the associated database
128
+ # with the given name.
129
+ def prepare(type, name=nil, *values)
130
+ ps = to_prepared_statement(type, values)
131
+ ps.extend(PreparedStatementMethods)
132
+ if name
133
+ ps.prepared_statement_name = name
134
+ db.prepared_statements[name] = ps
135
+ end
136
+ ps
137
+ end
138
+
139
+ private
140
+
141
+ # Extend the dataset with the MySQL stored procedure methods.
142
+ def prepare_extend_sproc(ds)
143
+ ds.extend(StoredProcedureMethods)
144
+ end
145
+
146
+ end
147
+ end
148
+ end
149
+ end
@@ -1,9 +1,13 @@
1
+ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
2
+
1
3
  module Sequel
2
4
  module Oracle
3
5
  module DatabaseMethods
4
6
  TEMPORARY = 'GLOBAL TEMPORARY '.freeze
5
7
  AUTOINCREMENT = ''.freeze
6
8
 
9
+ attr_accessor :autosequence
10
+
7
11
  def create_sequence(name, opts={})
8
12
  self << create_sequence_sql(name, opts)
9
13
  end
@@ -12,6 +16,10 @@ module Sequel
12
16
  self << create_trigger_sql(*args)
13
17
  end
14
18
 
19
+ def current_user
20
+ @current_user ||= get{sys_context('USERENV', 'CURRENT_USER')}
21
+ end
22
+
15
23
  def drop_sequence(name)
16
24
  self << drop_sequence_sql(name)
17
25
  end
@@ -26,10 +34,6 @@ module Sequel
26
34
  ds.map{|r| ds.send(:output_identifier, r[:tname])}
27
35
  end
28
36
 
29
- def table_exists?(name)
30
- from(:tab).filter(:tname =>dataset.send(:input_identifier, name), :tabtype => 'TABLE').count > 0
31
- end
32
-
33
37
  def views(opts={})
34
38
  ds = from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'VIEW')
35
39
  ds.map{|r| ds.send(:output_identifier, r[:tname])}
@@ -41,13 +45,37 @@ module Sequel
41
45
 
42
46
  private
43
47
 
44
- def auto_increment_sql
45
- AUTOINCREMENT
48
+ # Handle Oracle specific ALTER TABLE SQL
49
+ def alter_table_sql(table, op)
50
+ case op[:op]
51
+ when :add_column
52
+ if op[:primary_key]
53
+ sqls = []
54
+ sqls << alter_table_sql(table, op.merge(:primary_key=>nil))
55
+ if op[:auto_increment]
56
+ seq_name = default_sequence_name(table, op[:name])
57
+ sqls << drop_sequence_sql(seq_name)
58
+ sqls << create_sequence_sql(seq_name, op)
59
+ sqls << "UPDATE #{quote_schema_table(table)} SET #{quote_identifier(op[:name])} = #{seq_name}.nextval"
60
+ end
61
+ sqls << "ALTER TABLE #{quote_schema_table(table)} ADD PRIMARY KEY (#{quote_identifier(op[:name])})"
62
+ sqls
63
+ else
64
+ "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
65
+ end
66
+ when :set_column_null
67
+ "ALTER TABLE #{quote_schema_table(table)} MODIFY #{quote_identifier(op[:name])} #{op[:null] ? 'NULL' : 'NOT NULL'}"
68
+ when :set_column_type
69
+ "ALTER TABLE #{quote_schema_table(table)} MODIFY #{quote_identifier(op[:name])} #{type_literal(op)}"
70
+ when :set_column_default
71
+ "ALTER TABLE #{quote_schema_table(table)} MODIFY #{quote_identifier(op[:name])} DEFAULT #{literal(op[:default])}"
72
+ else
73
+ super(table, op)
74
+ end
46
75
  end
47
76
 
48
- # SQL fragment for showing a table is temporary
49
- def temporary_table_sql
50
- TEMPORARY
77
+ def auto_increment_sql
78
+ AUTOINCREMENT
51
79
  end
52
80
 
53
81
  def create_sequence_sql(name, opts={})
@@ -65,7 +93,7 @@ module Sequel
65
93
  drop_seq_statement = nil
66
94
  generator.columns.each do |c|
67
95
  if c[:auto_increment]
68
- c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
96
+ c[:sequence_name] ||= default_sequence_name(name, c[:name])
69
97
  unless c[:create_sequence] == false
70
98
  drop_seq_statement = drop_sequence_sql(c[:sequence_name])
71
99
  statements << create_sequence_sql(c[:sequence_name], c)
@@ -97,13 +125,94 @@ module Sequel
97
125
  sql
98
126
  end
99
127
 
128
+ def default_sequence_name(table, column)
129
+ "seq_#{table}_#{column}"
130
+ end
131
+
100
132
  def drop_sequence_sql(name)
101
133
  "DROP SEQUENCE #{quote_identifier(name)}"
102
134
  end
135
+
136
+ def remove_cached_schema(table)
137
+ @primary_key_sequences.delete(table)
138
+ super
139
+ end
140
+
141
+ def sequence_for_table(table)
142
+ return nil unless autosequence
143
+ @primary_key_sequences.fetch(table) do |key|
144
+ pk = schema(table).select{|k, v| v[:primary_key]}
145
+ @primary_key_sequences[table] = if pk.length == 1
146
+ seq = "seq_#{table}_#{pk.first.first}"
147
+ seq.to_sym unless from(:user_sequences).filter(:sequence_name=>input_identifier_meth.call(seq)).empty?
148
+ end
149
+ end
150
+ end
151
+
152
+ # Oracle's integer/:number type handles larger values than
153
+ # most other databases's bigint types, so it should be
154
+ # safe to use for Bignum.
155
+ def type_literal_generic_bignum(column)
156
+ :integer
157
+ end
158
+
159
+ # Oracle doesn't have a time type, so use timestamp for all
160
+ # time columns.
161
+ def type_literal_generic_time(column)
162
+ :timestamp
163
+ end
164
+
165
+ # Oracle doesn't have a boolean type or even a reasonable
166
+ # facsimile. Using a char(1) seems to be the recommended way.
167
+ def type_literal_generic_trueclass(column)
168
+ :'char(1)'
169
+ end
170
+
171
+ # SQL fragment for showing a table is temporary
172
+ def temporary_table_sql
173
+ TEMPORARY
174
+ end
103
175
  end
104
176
 
105
177
  module DatasetMethods
106
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order limit lock')
178
+ include EmulateOffsetWithRowNumber
179
+
180
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order lock')
181
+ ROW_NUMBER_EXPRESSION = 'ROWNUM'.lit.freeze
182
+
183
+ # Oracle needs to emulate bitwise operators and ILIKE/NOT ILIKE operators.
184
+ def complex_expression_sql(op, args)
185
+ case op
186
+ when :&
187
+ complex_expression_arg_pairs(args){|a, b| "CAST(BITAND(#{literal(a)}, #{literal(b)}) AS INTEGER)"}
188
+ when :|
189
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} - #{complex_expression_sql(:&, [a, b])} + #{literal(b)})"}
190
+ when :^
191
+ complex_expression_arg_pairs(args){|*x| "(#{complex_expression_sql(:|, x)} - #{complex_expression_sql(:&, x)})"}
192
+ when :'B~'
193
+ "((0 - #{literal(args.at(0))}) - 1)"
194
+ when :<<
195
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal b}))"}
196
+ when :>>
197
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal b}))"}
198
+ when :ILIKE, :'NOT ILIKE'
199
+ a, b = args
200
+ "(UPPER(#{literal(a)}) #{op == :ILIKE ? :LIKE : :'NOT LIKE'} UPPER(#{literal(b)}))"
201
+ else
202
+ super
203
+ end
204
+ end
205
+
206
+ # Oracle doesn't support CURRENT_TIME, as it doesn't have
207
+ # a type for storing just time values without a date, so
208
+ # use CURRENT_TIMESTAMP in its place.
209
+ def constant_sql(c)
210
+ if c == :CURRENT_TIME
211
+ super(:CURRENT_TIMESTAMP)
212
+ else
213
+ super
214
+ end
215
+ end
107
216
 
108
217
  # Oracle uses MINUS instead of EXCEPT, and doesn't support EXCEPT ALL
109
218
  def except(dataset, opts={})
@@ -113,18 +222,7 @@ module Sequel
113
222
  end
114
223
 
115
224
  def empty?
116
- db[:dual].where(exists).get(1) == nil
117
- end
118
-
119
- # If this dataset is associated with a sequence, return the most recently
120
- # inserted sequence value.
121
- def insert(*args)
122
- r = super
123
- if s = opts[:sequence]
124
- with_sql("SELECT #{literal(s)}.currval FROM dual").single_value.to_i
125
- else
126
- r
127
- end
225
+ db[:dual].where(unordered.exists).get(1) == nil
128
226
  end
129
227
 
130
228
  # Oracle requires SQL standard datetimes
@@ -139,16 +237,49 @@ module Sequel
139
237
  clone(:sequence=>s)
140
238
  end
141
239
 
240
+ # Handle LIMIT by using a unlimited subselect filtered with ROWNUM.
241
+ def select_sql
242
+ if (limit = @opts[:limit]) && !@opts[:sql]
243
+ ds = clone(:limit=>nil)
244
+ # Lock doesn't work in subselects, so don't use a subselect when locking.
245
+ # Don't use a subselect if custom SQL is used, as it breaks somethings.
246
+ ds = ds.from_self unless @opts[:lock]
247
+ subselect_sql(ds.where(SQL::ComplexExpression.new(:<=, ROW_NUMBER_EXPRESSION, limit)))
248
+ else
249
+ super
250
+ end
251
+ end
252
+
253
+ # Oracle requires recursive CTEs to have column aliases.
254
+ def recursive_cte_requires_column_aliases?
255
+ true
256
+ end
257
+
142
258
  # Oracle does not support INTERSECT ALL or EXCEPT ALL
143
259
  def supports_intersect_except_all?
144
260
  false
145
261
  end
262
+
263
+ # Oracle does not support IS TRUE.
264
+ def supports_is_true?
265
+ false
266
+ end
267
+
268
+ # Oracle does not support SELECT *, column
269
+ def supports_select_all_and_column?
270
+ false
271
+ end
146
272
 
147
273
  # Oracle supports timezones in literal timestamps.
148
274
  def supports_timestamp_timezones?
149
275
  true
150
276
  end
151
277
 
278
+ # Oracle does not support WHERE 'Y' for WHERE TRUE.
279
+ def supports_where_true?
280
+ false
281
+ end
282
+
152
283
  # Oracle supports window functions
153
284
  def supports_window_functions?
154
285
  true
@@ -167,16 +298,47 @@ module Sequel
167
298
  "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'".freeze
168
299
  end
169
300
 
301
+ # If this dataset is associated with a sequence, return the most recently
302
+ # inserted sequence value.
303
+ def execute_insert(sql, opts={})
304
+ f = @opts[:from]
305
+ super(sql, {:table=>(f.first if f), :sequence=>@opts[:sequence]}.merge(opts))
306
+ end
307
+
170
308
  # Use a colon for the timestamp offset, since Oracle appears to require it.
171
309
  def format_timestamp_offset(hour, minute)
172
310
  sprintf("%+03i:%02i", hour, minute)
173
311
  end
174
312
 
313
+ # Oracle doesn't support empty values when inserting.
314
+ def insert_supports_empty_values?
315
+ false
316
+ end
317
+
318
+ # Use string in hex format for blob data.
319
+ def literal_blob(v)
320
+ blob = "'"
321
+ v.each_byte{|x| blob << sprintf('%02x', x)}
322
+ blob << "'"
323
+ blob
324
+ end
325
+
326
+ # Oracle uses 'N' for false values.
327
+ def literal_false
328
+ "'N'"
329
+ end
330
+
175
331
  # Oracle uses the SQL standard of only doubling ' inside strings.
176
332
  def literal_string(v)
177
333
  "'#{v.gsub("'", "''")}'"
178
334
  end
179
335
 
336
+ # Oracle uses 'Y' for true values.
337
+ def literal_true
338
+ "'Y'"
339
+ end
340
+
341
+ # Use the Oracle-specific SQL clauses (no limit, since it is emulated).
180
342
  def select_clause_methods
181
343
  SELECT_CLAUSE_METHODS
182
344
  end
@@ -188,17 +350,6 @@ module Sequel
188
350
  def select_from_sql(sql)
189
351
  sql << " FROM #{source_list(@opts[:from] || ['DUAL'])}"
190
352
  end
191
-
192
- # Oracle requires a subselect to do limit and offset
193
- def select_limit_sql(sql)
194
- if limit = @opts[:limit]
195
- if (offset = @opts[:offset]) && (offset > 0)
196
- sql.replace("SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}")
197
- else
198
- sql.replace("SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}")
199
- end
200
- end
201
- end
202
353
  end
203
354
  end
204
355
  end