sequel 3.11.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/CHANGELOG +70 -0
  2. data/Rakefile +1 -1
  3. data/doc/active_record.rdoc +896 -0
  4. data/doc/advanced_associations.rdoc +46 -31
  5. data/doc/association_basics.rdoc +14 -9
  6. data/doc/dataset_basics.rdoc +3 -3
  7. data/doc/migration.rdoc +1011 -0
  8. data/doc/model_hooks.rdoc +198 -0
  9. data/doc/querying.rdoc +811 -86
  10. data/doc/release_notes/3.12.0.txt +304 -0
  11. data/doc/sharding.rdoc +17 -0
  12. data/doc/sql.rdoc +537 -0
  13. data/doc/validations.rdoc +501 -0
  14. data/lib/sequel/adapters/jdbc.rb +19 -27
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
  16. data/lib/sequel/adapters/mysql.rb +5 -4
  17. data/lib/sequel/adapters/odbc.rb +3 -2
  18. data/lib/sequel/adapters/shared/mssql.rb +7 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +2 -8
  21. data/lib/sequel/adapters/shared/sqlite.rb +2 -5
  22. data/lib/sequel/adapters/sqlite.rb +4 -4
  23. data/lib/sequel/core.rb +0 -1
  24. data/lib/sequel/database.rb +2 -1060
  25. data/lib/sequel/database/connecting.rb +227 -0
  26. data/lib/sequel/database/dataset.rb +58 -0
  27. data/lib/sequel/database/dataset_defaults.rb +127 -0
  28. data/lib/sequel/database/logging.rb +62 -0
  29. data/lib/sequel/database/misc.rb +246 -0
  30. data/lib/sequel/database/query.rb +390 -0
  31. data/lib/sequel/database/schema_generator.rb +7 -3
  32. data/lib/sequel/database/schema_methods.rb +351 -7
  33. data/lib/sequel/dataset/actions.rb +9 -2
  34. data/lib/sequel/dataset/misc.rb +6 -2
  35. data/lib/sequel/dataset/mutation.rb +3 -11
  36. data/lib/sequel/dataset/query.rb +49 -6
  37. data/lib/sequel/exceptions.rb +3 -0
  38. data/lib/sequel/extensions/migration.rb +395 -113
  39. data/lib/sequel/extensions/schema_dumper.rb +21 -13
  40. data/lib/sequel/model.rb +27 -25
  41. data/lib/sequel/model/associations.rb +72 -34
  42. data/lib/sequel/model/base.rb +74 -18
  43. data/lib/sequel/model/errors.rb +8 -1
  44. data/lib/sequel/plugins/active_model.rb +8 -0
  45. data/lib/sequel/plugins/association_pks.rb +87 -0
  46. data/lib/sequel/plugins/association_proxies.rb +8 -0
  47. data/lib/sequel/plugins/boolean_readers.rb +12 -6
  48. data/lib/sequel/plugins/caching.rb +14 -7
  49. data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
  50. data/lib/sequel/plugins/composition.rb +2 -1
  51. data/lib/sequel/plugins/force_encoding.rb +10 -7
  52. data/lib/sequel/plugins/hook_class_methods.rb +12 -11
  53. data/lib/sequel/plugins/identity_map.rb +9 -0
  54. data/lib/sequel/plugins/instance_hooks.rb +23 -13
  55. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  56. data/lib/sequel/plugins/many_through_many.rb +18 -4
  57. data/lib/sequel/plugins/nested_attributes.rb +1 -0
  58. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  59. data/lib/sequel/plugins/rcte_tree.rb +9 -8
  60. data/lib/sequel/plugins/schema.rb +8 -0
  61. data/lib/sequel/plugins/serialization.rb +1 -3
  62. data/lib/sequel/plugins/sharding.rb +135 -0
  63. data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
  64. data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
  65. data/lib/sequel/plugins/string_stripper.rb +26 -0
  66. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
  67. data/lib/sequel/plugins/timestamps.rb +15 -2
  68. data/lib/sequel/plugins/touch.rb +13 -0
  69. data/lib/sequel/plugins/update_primary_key.rb +48 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +8 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +17 -20
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/postgres_spec.rb +5 -5
  75. data/spec/core/core_sql_spec.rb +17 -1
  76. data/spec/core/database_spec.rb +17 -5
  77. data/spec/core/dataset_spec.rb +31 -8
  78. data/spec/core/schema_generator_spec.rb +8 -1
  79. data/spec/core/schema_spec.rb +13 -0
  80. data/spec/extensions/association_pks_spec.rb +85 -0
  81. data/spec/extensions/hook_class_methods_spec.rb +9 -9
  82. data/spec/extensions/migration_spec.rb +339 -219
  83. data/spec/extensions/schema_dumper_spec.rb +28 -17
  84. data/spec/extensions/sharding_spec.rb +272 -0
  85. data/spec/extensions/single_table_inheritance_spec.rb +92 -4
  86. data/spec/extensions/skip_create_refresh_spec.rb +17 -0
  87. data/spec/extensions/string_stripper_spec.rb +23 -0
  88. data/spec/extensions/update_primary_key_spec.rb +65 -0
  89. data/spec/extensions/validation_class_methods_spec.rb +5 -5
  90. data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
  91. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
  92. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  93. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  94. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
  95. data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
  96. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
  97. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
  98. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
  99. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
  100. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
  101. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
  102. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
  103. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
  104. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  105. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
  106. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
  107. data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
  108. data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
  109. data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
  110. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  111. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
  112. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  113. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
  114. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  115. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
  116. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
  117. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  118. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  119. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
  120. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
  121. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
  122. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
  123. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
  124. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
  125. data/spec/integration/eager_loader_test.rb +20 -20
  126. data/spec/integration/migrator_test.rb +187 -0
  127. data/spec/integration/plugin_test.rb +150 -0
  128. data/spec/integration/schema_test.rb +13 -2
  129. data/spec/model/associations_spec.rb +41 -14
  130. data/spec/model/base_spec.rb +69 -0
  131. data/spec/model/eager_loading_spec.rb +7 -3
  132. data/spec/model/record_spec.rb +79 -4
  133. data/spec/model/validations_spec.rb +21 -9
  134. metadata +66 -5
  135. data/doc/schema.rdoc +0 -36
  136. data/lib/sequel/database/schema_sql.rb +0 -320
@@ -0,0 +1,246 @@
1
+ module Sequel
2
+ class Database
3
+ # ---------------------
4
+ # :section: Miscellaneous methods
5
+ # These methods don't fit neatly into another category.
6
+ # ---------------------
7
+
8
+ # Converts a uri to an options hash. These options are then passed
9
+ # to a newly created database object.
10
+ def self.uri_to_options(uri) # :nodoc:
11
+ { :user => uri.user,
12
+ :password => uri.password,
13
+ :host => uri.host,
14
+ :port => uri.port,
15
+ :database => (m = /\/(.*)/.match(uri.path)) && (m[1]) }
16
+ end
17
+ private_class_method :uri_to_options
18
+
19
+ # The options for this database
20
+ attr_reader :opts
21
+
22
+ # Constructs a new instance of a database connection with the specified
23
+ # options hash.
24
+ #
25
+ # Sequel::Database is an abstract class that is not useful by itself.
26
+ #
27
+ # Takes the following options:
28
+ # * :default_schema : The default schema to use, should generally be nil
29
+ # * :disconnection_proc: A proc used to disconnect the connection.
30
+ # * :identifier_input_method: A string method symbol to call on identifiers going into the database
31
+ # * :identifier_output_method: A string method symbol to call on identifiers coming from the database
32
+ # * :loggers : An array of loggers to use.
33
+ # * :quote_identifiers : Whether to quote identifiers
34
+ # * :single_threaded : Whether to use a single-threaded connection pool
35
+ #
36
+ # All options given are also passed to the ConnectionPool. If a block
37
+ # is given, it is used as the connection_proc for the ConnectionPool.
38
+ def initialize(opts = {}, &block)
39
+ @opts ||= opts
40
+ @opts = connection_pool_default_options.merge(@opts)
41
+ @loggers = Array(@opts[:logger]) + Array(@opts[:loggers])
42
+ self.log_warn_duration = @opts[:log_warn_duration]
43
+ @opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
44
+ block ||= proc{|server| connect(server)}
45
+ @opts[:servers] = {} if @opts[:servers].is_a?(String)
46
+
47
+ @opts[:single_threaded] = @single_threaded = typecast_value_boolean(@opts.fetch(:single_threaded, @@single_threaded))
48
+ @schemas = {}
49
+ @default_schema = @opts.fetch(:default_schema, default_schema_default)
50
+ @prepared_statements = {}
51
+ @transactions = []
52
+ @identifier_input_method = nil
53
+ @identifier_output_method = nil
54
+ @quote_identifiers = nil
55
+ @pool = ConnectionPool.get_pool(@opts, &block)
56
+
57
+ ::Sequel::DATABASES.push(self)
58
+ end
59
+
60
+ # Cast the given type to a literal type
61
+ def cast_type_literal(type)
62
+ type_literal(:type=>type)
63
+ end
64
+
65
+ # Returns a string representation of the database object including the
66
+ # class name and the connection URI (or the opts if the URI
67
+ # cannot be constructed).
68
+ def inspect
69
+ "#<#{self.class}: #{(uri rescue opts).inspect}>"
70
+ end
71
+
72
+ # Proxy the literal call to the dataset, used for default values.
73
+ def literal(v)
74
+ schema_utility_dataset.literal(v)
75
+ end
76
+
77
+ # Default serial primary key options.
78
+ def serial_primary_key_options
79
+ {:primary_key => true, :type => Integer, :auto_increment => true}
80
+ end
81
+
82
+ # Whether the database and adapter support savepoints, false by default
83
+ def supports_savepoints?
84
+ false
85
+ end
86
+
87
+ # Typecast the value to the given column_type. Calls
88
+ # typecast_value_#{column_type} if the method exists,
89
+ # otherwise returns the value.
90
+ # This method should raise Sequel::InvalidValue if assigned value
91
+ # is invalid.
92
+ def typecast_value(column_type, value)
93
+ return nil if value.nil?
94
+ meth = "typecast_value_#{column_type}"
95
+ begin
96
+ respond_to?(meth, true) ? send(meth, value) : value
97
+ rescue ArgumentError, TypeError => e
98
+ raise Sequel.convert_exception_class(e, InvalidValue)
99
+ end
100
+ end
101
+
102
+ # Returns the URI identifying the database.
103
+ # This method can raise an error if the database used options
104
+ # instead of a connection string.
105
+ def uri
106
+ uri = URI::Generic.new(
107
+ self.class.adapter_scheme.to_s,
108
+ nil,
109
+ @opts[:host],
110
+ @opts[:port],
111
+ nil,
112
+ "/#{@opts[:database]}",
113
+ nil,
114
+ nil,
115
+ nil
116
+ )
117
+ uri.user = @opts[:user]
118
+ uri.password = @opts[:password] if uri.user
119
+ uri.to_s
120
+ end
121
+
122
+ # Explicit alias of uri for easier subclassing.
123
+ def url
124
+ uri
125
+ end
126
+
127
+ private
128
+
129
+ # Returns true when the object is considered blank.
130
+ # The only objects that are blank are nil, false,
131
+ # strings with all whitespace, and ones that respond
132
+ # true to empty?
133
+ def blank_object?(obj)
134
+ return obj.blank? if obj.respond_to?(:blank?)
135
+ case obj
136
+ when NilClass, FalseClass
137
+ true
138
+ when Numeric, TrueClass
139
+ false
140
+ when String
141
+ obj.strip.empty?
142
+ else
143
+ obj.respond_to?(:empty?) ? obj.empty? : false
144
+ end
145
+ end
146
+
147
+ # Which transaction errors to translate, blank by default.
148
+ def database_error_classes
149
+ []
150
+ end
151
+
152
+ # Convert the given exception to a DatabaseError, keeping message
153
+ # and traceback.
154
+ def raise_error(exception, opts={})
155
+ if !opts[:classes] || Array(opts[:classes]).any?{|c| exception.is_a?(c)}
156
+ raise Sequel.convert_exception_class(exception, opts[:disconnect] ? DatabaseDisconnectError : DatabaseError)
157
+ else
158
+ raise exception
159
+ end
160
+ end
161
+
162
+ # Typecast the value to an SQL::Blob
163
+ def typecast_value_blob(value)
164
+ value.is_a?(Sequel::SQL::Blob) ? value : Sequel::SQL::Blob.new(value)
165
+ end
166
+
167
+ # Typecast the value to true, false, or nil
168
+ def typecast_value_boolean(value)
169
+ case value
170
+ when false, 0, "0", /\Af(alse)?\z/i
171
+ false
172
+ else
173
+ blank_object?(value) ? nil : true
174
+ end
175
+ end
176
+
177
+ # Typecast the value to a Date
178
+ def typecast_value_date(value)
179
+ case value
180
+ when Date
181
+ value
182
+ when DateTime, Time
183
+ Date.new(value.year, value.month, value.day)
184
+ when String
185
+ Sequel.string_to_date(value)
186
+ when Hash
187
+ Date.new(*[:year, :month, :day].map{|x| (value[x] || value[x.to_s]).to_i})
188
+ else
189
+ raise InvalidValue, "invalid value for Date: #{value.inspect}"
190
+ end
191
+ end
192
+
193
+ # Typecast the value to a DateTime or Time depending on Sequel.datetime_class
194
+ def typecast_value_datetime(value)
195
+ raise(Sequel::InvalidValue, "invalid value for Datetime: #{value.inspect}") unless [DateTime, Date, Time, String, Hash].any?{|c| value.is_a?(c)}
196
+ klass = Sequel.datetime_class
197
+ if value.is_a?(Hash)
198
+ klass.send(klass == Time ? :mktime : :new, *[:year, :month, :day, :hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
199
+ else
200
+ Sequel.typecast_to_application_timestamp(value)
201
+ end
202
+ end
203
+
204
+ # Typecast the value to a BigDecimal
205
+ def typecast_value_decimal(value)
206
+ case value
207
+ when BigDecimal
208
+ value
209
+ when String, Numeric
210
+ BigDecimal.new(value.to_s)
211
+ else
212
+ raise InvalidValue, "invalid value for BigDecimal: #{value.inspect}"
213
+ end
214
+ end
215
+
216
+ # Typecast the value to a Float
217
+ def typecast_value_float(value)
218
+ Float(value)
219
+ end
220
+
221
+ # Typecast the value to an Integer
222
+ def typecast_value_integer(value)
223
+ Integer(value)
224
+ end
225
+
226
+ # Typecast the value to a String
227
+ def typecast_value_string(value)
228
+ value.to_s
229
+ end
230
+
231
+ # Typecast the value to a Time
232
+ def typecast_value_time(value)
233
+ case value
234
+ when Time
235
+ value
236
+ when String
237
+ Sequel.string_to_time(value)
238
+ when Hash
239
+ t = Time.now
240
+ Time.mktime(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
241
+ else
242
+ raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,390 @@
1
+ module Sequel
2
+ class Database
3
+ # ---------------------
4
+ # :section: Methods that execute queries and/or return results
5
+ # This methods generally execute SQL code on the database server.
6
+ # ---------------------
7
+
8
+ SQL_BEGIN = 'BEGIN'.freeze
9
+ SQL_COMMIT = 'COMMIT'.freeze
10
+ SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
11
+ SQL_ROLLBACK = 'ROLLBACK'.freeze
12
+ SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TO SAVEPOINT autopoint_%d'.freeze
13
+ SQL_SAVEPOINT = 'SAVEPOINT autopoint_%d'.freeze
14
+
15
+ TRANSACTION_BEGIN = 'Transaction.begin'.freeze
16
+ TRANSACTION_COMMIT = 'Transaction.commit'.freeze
17
+ TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
18
+
19
+ POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
20
+ MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
21
+ MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
22
+ STRING_DEFAULT_RE = /\A'(.*)'\z/
23
+
24
+ # The prepared statement objects for this database, keyed by name
25
+ attr_reader :prepared_statements
26
+
27
+ # Runs the supplied SQL statement string on the database server.
28
+ # Alias for run.
29
+ def <<(sql)
30
+ run(sql)
31
+ end
32
+
33
+ # Call the prepared statement with the given name with the given hash
34
+ # of arguments.
35
+ def call(ps_name, hash={})
36
+ prepared_statements[ps_name].call(hash)
37
+ end
38
+
39
+ # Executes the given SQL on the database. This method should be overridden in descendants.
40
+ # This method should not be called directly by user code.
41
+ def execute(sql, opts={})
42
+ raise NotImplemented, "#execute should be overridden by adapters"
43
+ end
44
+
45
+ # Method that should be used when submitting any DDL (Data Definition
46
+ # Language) SQL. By default, calls execute_dui.
47
+ # This method should not be called directly by user code.
48
+ def execute_ddl(sql, opts={}, &block)
49
+ execute_dui(sql, opts, &block)
50
+ end
51
+
52
+ # Method that should be used when issuing a DELETE, UPDATE, or INSERT
53
+ # statement. By default, calls execute.
54
+ # This method should not be called directly by user code.
55
+ def execute_dui(sql, opts={}, &block)
56
+ execute(sql, opts, &block)
57
+ end
58
+
59
+ # Method that should be used when issuing a INSERT
60
+ # statement. By default, calls execute_dui.
61
+ # This method should not be called directly by user code.
62
+ def execute_insert(sql, opts={}, &block)
63
+ execute_dui(sql, opts, &block)
64
+ end
65
+
66
+ # Returns a single value from the database, e.g.:
67
+ #
68
+ # # SELECT 1
69
+ # DB.get(1) #=> 1
70
+ #
71
+ # # SELECT version()
72
+ # DB.get(:version.sql_function) #=> ...
73
+ def get(*args, &block)
74
+ dataset.get(*args, &block)
75
+ end
76
+
77
+ # Return a hash containing index information. Hash keys are index name symbols.
78
+ # Values are subhashes with two keys, :columns and :unique. The value of :columns
79
+ # is an array of symbols of column names. The value of :unique is true or false
80
+ # depending on if the index is unique.
81
+ #
82
+ # Should not include the primary key index, functional indexes, or partial indexes.
83
+ def indexes(table, opts={})
84
+ raise NotImplemented, "#indexes should be overridden by adapters"
85
+ end
86
+
87
+ # Runs the supplied SQL statement string on the database server. Returns nil.
88
+ # Options:
89
+ # * :server - The server to run the SQL on.
90
+ def run(sql, opts={})
91
+ execute_ddl(sql, opts)
92
+ nil
93
+ end
94
+
95
+ # Parse the schema from the database.
96
+ # Returns the schema for the given table as an array with all members being arrays of length 2,
97
+ # the first member being the column name, and the second member being a hash of column information.
98
+ # Available options are:
99
+ #
100
+ # * :reload - Get fresh information from the database, instead of using
101
+ # cached information. If table_name is blank, :reload should be used
102
+ # unless you are sure that schema has not been called before with a
103
+ # table_name, otherwise you may only getting the schemas for tables
104
+ # that have been requested explicitly.
105
+ # * :schema - An explicit schema to use. It may also be implicitly provided
106
+ # via the table name.
107
+ def schema(table, opts={})
108
+ raise(Error, 'schema parsing is not implemented on this database') unless respond_to?(:schema_parse_table, true)
109
+
110
+ sch, table_name = schema_and_table(table)
111
+ quoted_name = quote_schema_table(table)
112
+ opts = opts.merge(:schema=>sch) if sch && !opts.include?(:schema)
113
+
114
+ @schemas.delete(quoted_name) if opts[:reload]
115
+ return @schemas[quoted_name] if @schemas[quoted_name]
116
+
117
+ cols = schema_parse_table(table_name, opts)
118
+ raise(Error, 'schema parsing returned no columns, table probably doesn\'t exist') if cols.nil? || cols.empty?
119
+ cols.each{|_,c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
120
+ @schemas[quoted_name] = cols
121
+ end
122
+
123
+ # Returns true if a table with the given name exists. This requires a query
124
+ # to the database.
125
+ def table_exists?(name)
126
+ begin
127
+ from(name).first
128
+ true
129
+ rescue
130
+ false
131
+ end
132
+ end
133
+
134
+ # Return all tables in the database as an array of symbols.
135
+ def tables(opts={})
136
+ raise NotImplemented, "#tables should be overridden by adapters"
137
+ end
138
+
139
+ # Starts a database transaction. When a database transaction is used,
140
+ # either all statements are successful or none of the statements are
141
+ # successful. Note that MySQL MyISAM tabels do not support transactions.
142
+ #
143
+ # The following options are respected:
144
+ #
145
+ # * :server - The server to use for the transaction
146
+ # * :savepoint - Whether to create a new savepoint for this transaction,
147
+ # only respected if the database adapter supports savepoints. By
148
+ # default Sequel will reuse an existing transaction, so if you want to
149
+ # use a savepoint you must use this option.
150
+ def transaction(opts={}, &block)
151
+ synchronize(opts[:server]) do |conn|
152
+ return yield(conn) if already_in_transaction?(conn, opts)
153
+ _transaction(conn, &block)
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ # Internal generic transaction method. Any exception raised by the given
160
+ # block will cause the transaction to be rolled back. If the exception is
161
+ # not Sequel::Rollback, the error will be reraised. If no exception occurs
162
+ # inside the block, the transaction is commited.
163
+ def _transaction(conn)
164
+ begin
165
+ add_transaction
166
+ t = begin_transaction(conn)
167
+ yield(conn)
168
+ rescue Exception => e
169
+ rollback_transaction(t) if t
170
+ transaction_error(e)
171
+ ensure
172
+ begin
173
+ commit_transaction(t) unless e
174
+ rescue Exception => e
175
+ raise_error(e, :classes=>database_error_classes)
176
+ ensure
177
+ remove_transaction(t)
178
+ end
179
+ end
180
+ end
181
+
182
+ # Add the current thread to the list of active transactions
183
+ def add_transaction
184
+ th = Thread.current
185
+ if supports_savepoints?
186
+ unless @transactions.include?(th)
187
+ th[:sequel_transaction_depth] = 0
188
+ @transactions << th
189
+ end
190
+ else
191
+ @transactions << th
192
+ end
193
+ end
194
+
195
+ # Whether the current thread/connection is already inside a transaction
196
+ def already_in_transaction?(conn, opts)
197
+ @transactions.include?(Thread.current) && (!supports_savepoints? || !opts[:savepoint])
198
+ end
199
+
200
+ # SQL to start a new savepoint
201
+ def begin_savepoint_sql(depth)
202
+ SQL_SAVEPOINT % depth
203
+ end
204
+
205
+ # Start a new database transaction on the given connection.
206
+ def begin_transaction(conn)
207
+ if supports_savepoints?
208
+ th = Thread.current
209
+ depth = th[:sequel_transaction_depth]
210
+ log_connection_execute(conn, depth > 0 ? begin_savepoint_sql(depth) : begin_transaction_sql)
211
+ th[:sequel_transaction_depth] += 1
212
+ else
213
+ log_connection_execute(conn, begin_transaction_sql)
214
+ end
215
+ conn
216
+ end
217
+
218
+ # SQL to BEGIN a transaction.
219
+ def begin_transaction_sql
220
+ SQL_BEGIN
221
+ end
222
+
223
+ # Convert the given default, which should be a database specific string, into
224
+ # a ruby object.
225
+ def column_schema_to_ruby_default(default, type)
226
+ return if default.nil?
227
+ orig_default = default
228
+ if database_type == :postgres and m = POSTGRES_DEFAULT_RE.match(default)
229
+ default = m[1] || m[2]
230
+ end
231
+ if database_type == :mssql and m = MSSQL_DEFAULT_RE.match(default)
232
+ default = m[1] || m[2]
233
+ end
234
+ if [:string, :blob, :date, :datetime, :time, :enum].include?(type)
235
+ if database_type == :mysql
236
+ return if [:date, :datetime, :time].include?(type) && MYSQL_TIMESTAMP_RE.match(default)
237
+ orig_default = default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
238
+ end
239
+ return unless m = STRING_DEFAULT_RE.match(default)
240
+ default = m[1].gsub("''", "'")
241
+ end
242
+ res = begin
243
+ case type
244
+ when :boolean
245
+ case default
246
+ when /[f0]/i
247
+ false
248
+ when /[t1]/i
249
+ true
250
+ end
251
+ when :string, :enum
252
+ default
253
+ when :blob
254
+ Sequel::SQL::Blob.new(default)
255
+ when :integer
256
+ Integer(default)
257
+ when :float
258
+ Float(default)
259
+ when :date
260
+ Sequel.string_to_date(default)
261
+ when :datetime
262
+ DateTime.parse(default)
263
+ when :time
264
+ Sequel.string_to_time(default)
265
+ when :decimal
266
+ BigDecimal.new(default)
267
+ end
268
+ rescue
269
+ nil
270
+ end
271
+ end
272
+
273
+ # SQL to commit a savepoint
274
+ def commit_savepoint_sql(depth)
275
+ SQL_RELEASE_SAVEPOINT % depth
276
+ end
277
+
278
+ # Commit the active transaction on the connection
279
+ def commit_transaction(conn)
280
+ if supports_savepoints?
281
+ depth = Thread.current[:sequel_transaction_depth]
282
+ log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
283
+ else
284
+ log_connection_execute(conn, commit_transaction_sql)
285
+ end
286
+ end
287
+
288
+ # SQL to COMMIT a transaction.
289
+ def commit_transaction_sql
290
+ SQL_COMMIT
291
+ end
292
+
293
+ # Method called on the connection object to execute SQL on the database,
294
+ # used by the transaction code.
295
+ def connection_execute_method
296
+ :execute
297
+ end
298
+
299
+ # Return a Method object for the dataset's output_identifier_method.
300
+ # Used in metadata parsing to make sure the returned information is in the
301
+ # correct format.
302
+ def input_identifier_meth
303
+ dataset.method(:input_identifier)
304
+ end
305
+
306
+ # Return a dataset that uses the default identifier input and output methods
307
+ # for this database. Used when parsing metadata so that column symbols are
308
+ # returned as expected.
309
+ def metadata_dataset
310
+ return @metadata_dataset if @metadata_dataset
311
+ ds = dataset
312
+ ds.identifier_input_method = identifier_input_method_default
313
+ ds.identifier_output_method = identifier_output_method_default
314
+ @metadata_dataset = ds
315
+ end
316
+
317
+ # Return a Method object for the dataset's output_identifier_method.
318
+ # Used in metadata parsing to make sure the returned information is in the
319
+ # correct format.
320
+ def output_identifier_meth
321
+ dataset.method(:output_identifier)
322
+ end
323
+
324
+ # SQL to ROLLBACK a transaction.
325
+ def rollback_transaction_sql
326
+ SQL_ROLLBACK
327
+ end
328
+
329
+ # Remove the cached schema for the given schema name
330
+ def remove_cached_schema(table)
331
+ @schemas.delete(quote_schema_table(table)) if @schemas
332
+ end
333
+
334
+ # Remove the current thread from the list of active transactions
335
+ def remove_transaction(conn)
336
+ th = Thread.current
337
+ @transactions.delete(th) if !supports_savepoints? || ((th[:sequel_transaction_depth] -= 1) <= 0)
338
+ end
339
+
340
+ # SQL to rollback to a savepoint
341
+ def rollback_savepoint_sql(depth)
342
+ SQL_ROLLBACK_TO_SAVEPOINT % depth
343
+ end
344
+
345
+ # Rollback the active transaction on the connection
346
+ def rollback_transaction(conn)
347
+ if supports_savepoints?
348
+ depth = Thread.current[:sequel_transaction_depth]
349
+ log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
350
+ else
351
+ log_connection_execute(conn, rollback_transaction_sql)
352
+ end
353
+ end
354
+
355
+ # Match the database's column type to a ruby type via a
356
+ # regular expression. The following ruby types are supported:
357
+ # integer, string, date, datetime, boolean, and float.
358
+ def schema_column_type(db_type)
359
+ case db_type
360
+ when /\Ainterval\z/io
361
+ :interval
362
+ when /\A(character( varying)?|n?(var)?char|n?text)/io
363
+ :string
364
+ when /\A(int(eger)?|(big|small|tiny)int)/io
365
+ :integer
366
+ when /\Adate\z/io
367
+ :date
368
+ when /\A((small)?datetime|timestamp( with(out)? time zone)?)\z/io
369
+ :datetime
370
+ when /\Atime( with(out)? time zone)?\z/io
371
+ :time
372
+ when /\A(boolean|bit)\z/io
373
+ :boolean
374
+ when /\A(real|float|double( precision)?)\z/io
375
+ :float
376
+ when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
377
+ $1 && $1 == '0' ? :integer : :decimal
378
+ when /bytea|blob|image|(var)?binary/io
379
+ :blob
380
+ when /\Aenum/io
381
+ :enum
382
+ end
383
+ end
384
+
385
+ # Raise a database error unless the exception is an Rollback.
386
+ def transaction_error(e)
387
+ raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
388
+ end
389
+ end
390
+ end