sequel 3.21.0 → 3.28.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 (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. metadata +85 -34
@@ -0,0 +1,190 @@
1
+ module Sequel
2
+ # The +ASTTransformer+ class is designed to handle the abstract syntax trees
3
+ # that Sequel uses internally and produce modified copies of them. By itself
4
+ # it only produces a straight copy. It's designed to be subclassed and have
5
+ # subclasses returned modified copies of the specific nodes that need to
6
+ # be modified.
7
+ class ASTTransformer
8
+ # Return +obj+ or a potentially transformed version of it.
9
+ def transform(obj)
10
+ v(obj)
11
+ end
12
+
13
+ private
14
+
15
+ # Recursive version that handles all of Sequel's internal object types
16
+ # and produces copies of them.
17
+ def v(o)
18
+ case o
19
+ when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
20
+ o
21
+ when Array
22
+ o.map{|x| v(x)}
23
+ when Hash
24
+ h = {}
25
+ o.each{|k, val| h[v(k)] = v(val)}
26
+ h
27
+ when SQL::ComplexExpression
28
+ SQL::ComplexExpression.new(o.op, *v(o.args))
29
+ when SQL::Identifier
30
+ SQL::Identifier.new(v(o.value))
31
+ when SQL::QualifiedIdentifier
32
+ SQL::QualifiedIdentifier.new(v(o.table), v(o.column))
33
+ when SQL::OrderedExpression
34
+ SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls)
35
+ when SQL::AliasedExpression
36
+ SQL::AliasedExpression.new(v(o.expression), o.aliaz)
37
+ when SQL::CaseExpression
38
+ args = [v(o.conditions), v(o.default)]
39
+ args << v(o.expression) if o.expression?
40
+ SQL::CaseExpression.new(*args)
41
+ when SQL::Cast
42
+ SQL::Cast.new(v(o.expr), o.type)
43
+ when SQL::Function
44
+ SQL::Function.new(o.f, *v(o.args))
45
+ when SQL::Subscript
46
+ SQL::Subscript.new(v(o.f), v(o.sub))
47
+ when SQL::WindowFunction
48
+ SQL::WindowFunction.new(v(o.function), v(o.window))
49
+ when SQL::Window
50
+ opts = o.opts.dup
51
+ opts[:partition] = v(opts[:partition]) if opts[:partition]
52
+ opts[:order] = v(opts[:order]) if opts[:order]
53
+ SQL::Window.new(opts)
54
+ when SQL::PlaceholderLiteralString
55
+ args = if o.args.is_a?(Hash)
56
+ h = {}
57
+ o.args.each{|k,val| h[k] = v(val)}
58
+ h
59
+ else
60
+ v(o.args)
61
+ end
62
+ SQL::PlaceholderLiteralString.new(o.str, args, o.parens)
63
+ when SQL::JoinOnClause
64
+ SQL::JoinOnClause.new(v(o.on), o.join_type, v(o.table), v(o.table_alias))
65
+ when SQL::JoinUsingClause
66
+ SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.table), v(o.table_alias))
67
+ when SQL::JoinClause
68
+ SQL::JoinClause.new(o.join_type, v(o.table), v(o.table_alias))
69
+ else
70
+ o
71
+ end
72
+ end
73
+ end
74
+
75
+ # Handles qualifying existing datasets, so that unqualified columns
76
+ # in the dataset are qualified with a given table name.
77
+ class Qualifier < ASTTransformer
78
+ # Store the dataset to use as the basis for qualification,
79
+ # and the table used to qualify unqualified columns.
80
+ def initialize(ds, table)
81
+ @ds = ds
82
+ @table = table
83
+ end
84
+
85
+ private
86
+
87
+ # Turn <tt>SQL::Identifier</tt>s and symbols that aren't implicitly
88
+ # qualified into <tt>SQL::QualifiedIdentifier</tt>s. For symbols that
89
+ # are not implicitly qualified by are implicitly aliased, return an
90
+ # <tt>SQL::AliasedExpression</tt>s with a qualified version of the symbol.
91
+ def v(o)
92
+ case o
93
+ when Symbol
94
+ t, column, aliaz = @ds.send(:split_symbol, o)
95
+ if t
96
+ o
97
+ elsif aliaz
98
+ SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(@table, SQL::Identifier.new(column)), aliaz)
99
+ else
100
+ SQL::QualifiedIdentifier.new(@table, o)
101
+ end
102
+ when SQL::Identifier
103
+ SQL::QualifiedIdentifier.new(@table, o)
104
+ when SQL::QualifiedIdentifier, SQL::JoinClause
105
+ # Return these directly, so we don't accidentally qualify symbols in them.
106
+ o
107
+ else
108
+ super
109
+ end
110
+ end
111
+ end
112
+
113
+ # +Unbinder+ is used to take a dataset filter and return a modified version
114
+ # that unbinds already bound values and returns a dataset with bound value
115
+ # placeholders and a hash of bind values. You can then prepare the dataset
116
+ # and use the bound variables to execute it with the same values.
117
+ #
118
+ # This class only does a limited form of unbinding where the variable names
119
+ # and values can be associated unambiguously. The only cases it handles
120
+ # are <tt>SQL::ComplexExpression<tt> with an operator in +UNBIND_OPS+, a
121
+ # first argument that's an instance of a member of +UNBIND_KEY_CLASSES+, and
122
+ # a second argument that's an instance of a member of +UNBIND_VALUE_CLASSES+.
123
+ #
124
+ # So it can handle cases like:
125
+ #
126
+ # DB.filter(:a=>1).exclude(:b=>2).where{c > 3}
127
+ #
128
+ # But it cannot handle cases like:
129
+ #
130
+ # DB.filter(:a + 1 < 0)
131
+ class Unbinder < ASTTransformer
132
+ # The <tt>SQL::ComplexExpression<tt> operates that will be considered
133
+ # for transformation.
134
+ UNBIND_OPS = [:'=', :'!=', :<, :>, :<=, :>=]
135
+
136
+ # The key classes (first argument of the ComplexExpression) that will
137
+ # considered for transformation.
138
+ UNBIND_KEY_CLASSES = [Symbol, SQL::Identifier, SQL::QualifiedIdentifier]
139
+
140
+ # The value classes (second argument of the ComplexExpression) that
141
+ # will be considered for transformation.
142
+ UNBIND_VALUE_CLASSES = [Numeric, String, Date, Time]
143
+
144
+ # The hash of bind variables that were extracted from the dataset filter.
145
+ attr_reader :binds
146
+
147
+ # Intialize an empty +binds+ hash.
148
+ def initialize
149
+ @binds = {}
150
+ end
151
+
152
+ private
153
+
154
+ # Create a suitable bound variable key for the object, which should be
155
+ # an instance of one of the +UNBIND_KEY_CLASSES+.
156
+ def bind_key(obj)
157
+ case obj
158
+ when Symbol, String
159
+ obj
160
+ when SQL::Identifier
161
+ bind_key(obj.value)
162
+ when SQL::QualifiedIdentifier
163
+ :"#{bind_key(obj.table)}.#{bind_key(obj.column)}"
164
+ else
165
+ raise Error, "unhandled object in Sequel::Unbinder#bind_key: #{obj}"
166
+ end
167
+ end
168
+
169
+ # Handle <tt>SQL::ComplexExpression</tt> instances with suitable ops
170
+ # and arguments, substituting the value with a bound variable placeholder
171
+ # and assigning it an entry in the +binds+ hash with a matching key.
172
+ def v(o)
173
+ if o.is_a?(SQL::ComplexExpression) && UNBIND_OPS.include?(o.op)
174
+ l, r = o.args
175
+ if UNBIND_KEY_CLASSES.any?{|c| l.is_a?(c)} && UNBIND_VALUE_CLASSES.any?{|c| r.is_a?(c)} && !r.is_a?(LiteralString)
176
+ key = bind_key(l)
177
+ if (old = binds[key]) && old != r
178
+ raise UnbindDuplicate, "two different values for #{key.inspect}: #{[r, old].inspect}"
179
+ end
180
+ binds[key] = r
181
+ SQL::ComplexExpression.new(o.op, l, :"$#{key}")
182
+ else
183
+ super
184
+ end
185
+ else
186
+ super
187
+ end
188
+ end
189
+ end
190
+ end
@@ -83,9 +83,10 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
83
83
  end
84
84
  yield conn
85
85
  rescue Sequel::DatabaseDisconnectError
86
- @disconnection_proc.call(conn) if @disconnection_proc && conn
87
- @allocated.delete(t)
86
+ oconn = conn
88
87
  conn = nil
88
+ @disconnection_proc.call(oconn) if @disconnection_proc && oconn
89
+ @allocated.delete(t)
89
90
  raise
90
91
  ensure
91
92
  sync{release(t)} if conn
@@ -53,7 +53,7 @@ class Sequel::ConnectionPool
53
53
 
54
54
  # Return a connection pool class based on the given options.
55
55
  def connection_pool_class(opts)
56
- opts[:pool_class] || CONNECTION_POOL_MAP[[!!opts[:single_threaded], !!opts[:servers]]]
56
+ CONNECTION_POOL_MAP[opts[:pool_class]] || opts[:pool_class] || CONNECTION_POOL_MAP[[!!opts[:single_threaded], !!opts[:servers]]]
57
57
  end
58
58
  end
59
59
  extend ClassMethods
data/lib/sequel/core.rb CHANGED
@@ -188,7 +188,7 @@ module Sequel
188
188
  # This is used to ensure that the files loaded are from the same version of
189
189
  # Sequel as this file.
190
190
  def self.require(files, subdir=nil)
191
- Array(files).each{|f| super("#{File.dirname(__FILE__)}/#{"#{subdir}/" if subdir}#{f}")}
191
+ Array(files).each{|f| super("#{File.dirname(__FILE__).untaint}/#{"#{subdir}/" if subdir}#{f}")}
192
192
  end
193
193
 
194
194
  # Set whether to set the single threaded mode for all databases by default. By default,
@@ -228,12 +228,13 @@ module Sequel
228
228
  end
229
229
  end
230
230
 
231
- # Converts the given +string+ into a +Time+ object.
231
+ # Converts the given +string+ into a <tt>Sequel::SQLTime</tt> object.
232
232
  #
233
- # Sequel.string_to_datetime('10:20:30') # Time.parse('10:20:30')
233
+ # v = Sequel.string_to_time('10:20:30') # Sequel::SQLTime.parse('10:20:30')
234
+ # DB.literal(v) # => '10:20:30'
234
235
  def self.string_to_time(string)
235
236
  begin
236
- Time.parse(string)
237
+ SQLTime.parse(string)
237
238
  rescue => e
238
239
  raise convert_exception_class(e, InvalidValue)
239
240
  end
@@ -292,7 +293,7 @@ module Sequel
292
293
 
293
294
  private_class_method :adapter_method, :def_adapter_method
294
295
 
295
- require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
296
+ require(%w"metaprogramming sql connection_pool exceptions dataset database timezones ast_transformer version")
296
297
  require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
297
298
 
298
299
  # Add the database adapter class methods to Sequel via metaprogramming
@@ -1,12 +1,12 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods relating to adapters, connecting, disconnecting, and sharding
4
+ # :section: 4 - Methods relating to adapters, connecting, disconnecting, and sharding
5
5
  # This methods involve the Database's connection pool.
6
6
  # ---------------------
7
7
 
8
8
  # Array of supported database adapters
9
- ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
9
+ ADAPTERS = %w'ado amalgalite db2 dbi do firebird ibmdb informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
10
10
 
11
11
  # Whether to use the single threaded connection pool by default
12
12
  @@single_threaded = false
@@ -54,7 +54,7 @@ module Sequel
54
54
  c = adapter_class(scheme)
55
55
  uri_options = c.send(:uri_to_options, uri)
56
56
  uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
57
- uri_options.entries.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
57
+ uri_options.to_a.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
58
58
  opts = uri_options.merge(opts)
59
59
  opts[:adapter] = scheme
60
60
  end
@@ -155,8 +155,8 @@ module Sequel
155
155
 
156
156
  # Disconnects all available connections from the connection pool. Any
157
157
  # connections currently in use will not be disconnected. Options:
158
- # * :servers - Should be a symbol specifing the server to disconnect from,
159
- # or an array of symbols to specify multiple servers.
158
+ # :servers :: Should be a symbol specifing the server to disconnect from,
159
+ # or an array of symbols to specify multiple servers.
160
160
  #
161
161
  # Example:
162
162
  #
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods that create datasets
4
+ # :section: 3 - Methods that create datasets
5
5
  # These methods all return instances of this database's dataset class.
6
6
  # ---------------------
7
7
 
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods that set defaults for created datasets
4
+ # :section: 5 - Methods that set defaults for created datasets
5
5
  # This methods change the default behavior of this database's datasets.
6
6
  # ---------------------
7
7
 
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods relating to logging
4
+ # :section: 6 - Methods relating to logging
5
5
  # This methods affect relating to the logging of executed SQL.
6
6
  # ---------------------
7
7
 
@@ -1,13 +1,13 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Miscellaneous methods
4
+ # :section: 7 - Miscellaneous methods
5
5
  # These methods don't fit neatly into another category.
6
6
  # ---------------------
7
7
 
8
8
  # Converts a uri to an options hash. These options are then passed
9
9
  # to a newly created database object.
10
- def self.uri_to_options(uri) # :nodoc:
10
+ def self.uri_to_options(uri)
11
11
  { :user => uri.user,
12
12
  :password => uri.password,
13
13
  :host => uri.host,
@@ -90,6 +90,12 @@ module Sequel
90
90
  {:primary_key => true, :type => Integer, :auto_increment => true}
91
91
  end
92
92
 
93
+ # Whether the database supports CREATE TABLE IF NOT EXISTS syntax,
94
+ # false by default.
95
+ def supports_create_table_if_not_exists?
96
+ false
97
+ end
98
+
93
99
  # Whether the database and adapter support prepared transactions
94
100
  # (two-phase commit), false by default.
95
101
  def supports_prepared_transactions?
@@ -175,12 +181,17 @@ module Sequel
175
181
  def database_error_classes
176
182
  []
177
183
  end
184
+
185
+ # Return true if exception represents a disconnect error, false otherwise.
186
+ def disconnect_error?(exception, opts)
187
+ opts[:disconnect]
188
+ end
178
189
 
179
190
  # Convert the given exception to a DatabaseError, keeping message
180
191
  # and traceback.
181
192
  def raise_error(exception, opts={})
182
193
  if !opts[:classes] || Array(opts[:classes]).any?{|c| exception.is_a?(c)}
183
- raise Sequel.convert_exception_class(exception, opts[:disconnect] ? DatabaseDisconnectError : DatabaseError)
194
+ raise Sequel.convert_exception_class(exception, disconnect_error?(exception, opts) ? DatabaseDisconnectError : DatabaseError)
184
195
  else
185
196
  raise exception
186
197
  end
@@ -204,10 +215,10 @@ module Sequel
204
215
  # Typecast the value to a Date
205
216
  def typecast_value_date(value)
206
217
  case value
207
- when Date
208
- value
209
218
  when DateTime, Time
210
219
  Date.new(value.year, value.month, value.day)
220
+ when Date
221
+ value
211
222
  when String
212
223
  Sequel.string_to_date(value)
213
224
  when Hash
@@ -219,12 +230,7 @@ module Sequel
219
230
 
220
231
  # Typecast the value to a DateTime or Time depending on Sequel.datetime_class
221
232
  def typecast_value_datetime(value)
222
- klass = Sequel.datetime_class
223
- if value.is_a?(Hash)
224
- klass.send(klass == Time ? :mktime : :new, *[:year, :month, :day, :hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
225
- else
226
- Sequel.typecast_to_application_timestamp(value)
227
- end
233
+ Sequel.typecast_to_application_timestamp(value)
228
234
  end
229
235
 
230
236
  # Typecast the value to a BigDecimal
@@ -244,9 +250,21 @@ module Sequel
244
250
  Float(value)
245
251
  end
246
252
 
247
- # Typecast the value to an Integer
248
- def typecast_value_integer(value)
249
- Integer(value)
253
+ # Used for checking/removing leading zeroes from strings so they don't get
254
+ # interpreted as octal.
255
+ LEADING_ZERO_RE = /\A0+(\d)/.freeze
256
+ if RUBY_VERSION >= '1.9'
257
+ # Typecast the value to an Integer
258
+ def typecast_value_integer(value)
259
+ (value.is_a?(String) && value =~ LEADING_ZERO_RE) ? Integer(value, 10) : Integer(value)
260
+ end
261
+ else
262
+ # Replacement string when replacing leading zeroes.
263
+ LEADING_ZERO_REP = "\\1".freeze
264
+ # Typecast the value to an Integer
265
+ def typecast_value_integer(value)
266
+ Integer(value.is_a?(String) ? value.sub(LEADING_ZERO_RE, LEADING_ZERO_REP) : value)
267
+ end
250
268
  end
251
269
 
252
270
  # Typecast the value to a String
@@ -258,12 +276,15 @@ module Sequel
258
276
  def typecast_value_time(value)
259
277
  case value
260
278
  when Time
261
- value
279
+ if value.is_a?(SQLTime)
280
+ value
281
+ else
282
+ SQLTime.create(value.hour, value.min, value.sec, value.respond_to?(:nsec) ? value.nsec/1000.0 : value.usec)
283
+ end
262
284
  when String
263
285
  Sequel.string_to_time(value)
264
286
  when Hash
265
- t = Time.now
266
- Time.mktime(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
287
+ SQLTime.create(*[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
267
288
  else
268
289
  raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
269
290
  end
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods that execute queries and/or return results
4
+ # :section: 1 - Methods that execute queries and/or return results
5
5
  # This methods generally execute SQL code on the database server.
6
6
  # ---------------------
7
7
 
@@ -82,12 +82,12 @@ module Sequel
82
82
  #
83
83
  # DB.get(1) # SELECT 1
84
84
  # # => 1
85
- # DB.get{version{}} # SELECT server_version()
85
+ # DB.get{server_version{}} # SELECT server_version()
86
86
  def get(*args, &block)
87
87
  dataset.get(*args, &block)
88
88
  end
89
89
 
90
- # Return a hash containing index information. Hash keys are index name symbols.
90
+ # Return a hash containing index information for the table. Hash keys are index name symbols.
91
91
  # Values are subhashes with two keys, :columns and :unique. The value of :columns
92
92
  # is an array of symbols of column names. The value of :unique is true or false
93
93
  # depending on if the index is unique.
@@ -110,7 +110,6 @@ module Sequel
110
110
  nil
111
111
  end
112
112
 
113
- # Parse the schema from the database.
114
113
  # Returns the schema for the given table as an array with all members being arrays of length 2,
115
114
  # the first member being the column name, and the second member being a hash of column information.
116
115
  # Available options are:
@@ -129,7 +128,8 @@ module Sequel
129
128
  # it means that primary key information is unavailable, not that the column
130
129
  # is not a primary key.
131
130
  # :ruby_default :: The database default for the column, as a ruby object. In many cases, complex
132
- # database defaults cannot be parsed into ruby objects.
131
+ # database defaults cannot be parsed into ruby objects, in which case nil will be
132
+ # used as the value.
133
133
  # :type :: A symbol specifying the type, such as :integer or :string.
134
134
  #
135
135
  # Example:
@@ -169,13 +169,14 @@ module Sequel
169
169
  # to the database.
170
170
  #
171
171
  # DB.table_exists?(:foo) # => false
172
+ # # SELECT * FROM foo LIMIT 1
172
173
  def table_exists?(name)
173
- begin
174
- from(name).first
175
- true
176
- rescue
177
- false
178
- end
174
+ sch, table_name = schema_and_table(name)
175
+ name = SQL::QualifiedIdentifier.new(sch, table_name) if sch
176
+ from(name).first
177
+ true
178
+ rescue
179
+ false
179
180
  end
180
181
 
181
182
  # Return all tables in the database as an array of symbols.
@@ -210,6 +211,13 @@ module Sequel
210
211
  end
211
212
  end
212
213
 
214
+ # Return all views in the database as an array of symbols.
215
+ #
216
+ # DB.views # => [:gold_albums, :artists_with_many_albums]
217
+ def views(opts={})
218
+ raise NotImplemented, "#views should be overridden by adapters"
219
+ end
220
+
213
221
  private
214
222
 
215
223
  # Internal generic transaction method. Any exception raised by the given
@@ -223,18 +231,18 @@ module Sequel
223
231
  yield(conn)
224
232
  rescue Exception => e
225
233
  rollback_transaction(t, opts) if t
226
- transaction_error(e)
234
+ transaction_error(e, :conn=>conn)
227
235
  ensure
228
236
  begin
229
- commit_transaction(t, opts) unless e
237
+ commit_or_rollback_transaction(e, t, opts)
230
238
  rescue Exception => e
231
- raise_error(e, :classes=>database_error_classes)
239
+ raise_error(e, :classes=>database_error_classes, :conn=>conn)
232
240
  ensure
233
241
  remove_transaction(t)
234
242
  end
235
243
  end
236
244
  end
237
-
245
+
238
246
  # Add the current thread to the list of active transactions
239
247
  def add_transaction
240
248
  th = Thread.current
@@ -335,6 +343,29 @@ module Sequel
335
343
  end
336
344
  end
337
345
 
346
+ if (! defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and RUBY_VERSION < '1.9'
347
+ # Whether to commit the current transaction. On ruby 1.8 and rubinius,
348
+ # Thread.current.status is checked because Thread#kill skips rescue
349
+ # blocks (so exception would be nil), but the transaction should
350
+ # still be rolled back.
351
+ def commit_or_rollback_transaction(exception, thread, opts)
352
+ unless exception
353
+ if Thread.current.status == 'aborting'
354
+ rollback_transaction(thread, opts)
355
+ else
356
+ commit_transaction(thread, opts)
357
+ end
358
+ end
359
+ end
360
+ else
361
+ # Whether to commit the current transaction. On ruby 1.9 and JRuby,
362
+ # transactions will be committed if Thread#kill is used on an thread
363
+ # that has a transaction open, and there isn't a work around.
364
+ def commit_or_rollback_transaction(exception, thread, opts)
365
+ commit_transaction(thread, opts) unless exception
366
+ end
367
+ end
368
+
338
369
  # SQL to commit a savepoint
339
370
  def commit_savepoint_sql(depth)
340
371
  SQL_RELEASE_SAVEPOINT % depth
@@ -434,13 +465,13 @@ module Sequel
434
465
  :datetime
435
466
  when /\Atime( with(out)? time zone)?\z/io
436
467
  :time
437
- when /\A(boolean|bit)\z/io
468
+ when /\A(bool(ean)?)\z/io
438
469
  :boolean
439
470
  when /\A(real|float|double( precision)?)\z/io
440
471
  :float
441
472
  when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
442
473
  $1 && $1 == '0' ? :integer : :decimal
443
- when /bytea|blob|image|(var)?binary/io
474
+ when /bytea|[bc]lob|image|(var)?binary/io
444
475
  :blob
445
476
  when /\Aenum/io
446
477
  :enum
@@ -460,8 +491,8 @@ module Sequel
460
491
  end
461
492
 
462
493
  # Raise a database error unless the exception is an Rollback.
463
- def transaction_error(e)
464
- raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
494
+ def transaction_error(e, opts={})
495
+ raise_error(e, opts.merge(:classes=>database_error_classes)) unless e.is_a?(Rollback)
465
496
  end
466
497
  end
467
498
  end
@@ -86,7 +86,8 @@ module Sequel
86
86
  # :index :: Create an index on this column.
87
87
  # :key :: For foreign key columns, the column in the associated table
88
88
  # that this column references. Unnecessary if this column
89
- # references the primary key of the associated table.
89
+ # references the primary key of the associated table, except if you are
90
+ # using MySQL.
90
91
  # :null :: Mark the column as allowing NULL values (if true),
91
92
  # or not allowing NULL values (if false). If unspecified, will default
92
93
  # to whatever the database default is.
@@ -329,15 +330,17 @@ module Sequel
329
330
  # Remove a column from the DDL for the table.
330
331
  #
331
332
  # drop_column(:artist_id) # DROP COLUMN artist_id
332
- def drop_column(name)
333
- @operations << {:op => :drop_column, :name => name}
333
+ # drop_column(:artist_id, :cascade=>true) # DROP COLUMN artist_id CASCADE
334
+ def drop_column(name, opts={})
335
+ @operations << {:op => :drop_column, :name => name}.merge(opts)
334
336
  end
335
337
 
336
338
  # Remove a constraint from the DDL for the table.
337
339
  #
338
340
  # drop_constraint(:unique_name) # DROP CONSTRAINT unique_name
339
- def drop_constraint(name)
340
- @operations << {:op => :drop_constraint, :name => name}
341
+ # drop_constraint(:unique_name, :cascade=>true) # DROP CONSTRAINT unique_name CASCADE
342
+ def drop_constraint(name, opts={})
343
+ @operations << {:op => :drop_constraint, :name => name}.merge(opts)
341
344
  end
342
345
 
343
346
  # Remove an index from the DDL for the table.