sequel 3.11.0 → 3.12.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 (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