sequel 3.8.0 → 3.9.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 (65) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +6 -28
  3. data/bin/sequel +7 -2
  4. data/doc/release_notes/3.9.0.txt +233 -0
  5. data/lib/sequel/adapters/ado.rb +4 -8
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/dbi.rb +3 -3
  8. data/lib/sequel/adapters/do.rb +7 -13
  9. data/lib/sequel/adapters/jdbc.rb +10 -16
  10. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  11. data/lib/sequel/adapters/mysql.rb +10 -23
  12. data/lib/sequel/adapters/odbc.rb +6 -10
  13. data/lib/sequel/adapters/postgres.rb +0 -5
  14. data/lib/sequel/adapters/shared/mssql.rb +17 -9
  15. data/lib/sequel/adapters/shared/mysql.rb +16 -7
  16. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  17. data/lib/sequel/adapters/sqlite.rb +2 -1
  18. data/lib/sequel/connection_pool.rb +67 -349
  19. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  20. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  21. data/lib/sequel/connection_pool/single.rb +29 -0
  22. data/lib/sequel/connection_pool/threaded.rb +150 -0
  23. data/lib/sequel/core.rb +46 -15
  24. data/lib/sequel/database.rb +11 -9
  25. data/lib/sequel/dataset/convenience.rb +23 -0
  26. data/lib/sequel/dataset/graph.rb +2 -2
  27. data/lib/sequel/dataset/query.rb +9 -5
  28. data/lib/sequel/dataset/sql.rb +87 -12
  29. data/lib/sequel/extensions/inflector.rb +8 -1
  30. data/lib/sequel/extensions/schema_dumper.rb +3 -4
  31. data/lib/sequel/model/associations.rb +5 -43
  32. data/lib/sequel/model/base.rb +9 -2
  33. data/lib/sequel/model/default_inflections.rb +1 -1
  34. data/lib/sequel/model/exceptions.rb +11 -1
  35. data/lib/sequel/model/inflections.rb +8 -1
  36. data/lib/sequel/model/plugins.rb +2 -12
  37. data/lib/sequel/plugins/active_model.rb +5 -0
  38. data/lib/sequel/plugins/association_dependencies.rb +1 -1
  39. data/lib/sequel/plugins/many_through_many.rb +1 -1
  40. data/lib/sequel/plugins/optimistic_locking.rb +65 -0
  41. data/lib/sequel/plugins/single_table_inheritance.rb +14 -3
  42. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  43. data/lib/sequel/sql.rb +2 -2
  44. data/lib/sequel/timezones.rb +2 -2
  45. data/lib/sequel/version.rb +1 -1
  46. data/spec/adapters/mssql_spec.rb +19 -0
  47. data/spec/adapters/mysql_spec.rb +4 -0
  48. data/spec/adapters/postgres_spec.rb +180 -0
  49. data/spec/adapters/spec_helper.rb +15 -1
  50. data/spec/core/connection_pool_spec.rb +119 -78
  51. data/spec/core/database_spec.rb +41 -50
  52. data/spec/core/dataset_spec.rb +115 -4
  53. data/spec/extensions/active_model_spec.rb +40 -34
  54. data/spec/extensions/boolean_readers_spec.rb +1 -1
  55. data/spec/extensions/migration_spec.rb +43 -38
  56. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  57. data/spec/extensions/schema_dumper_spec.rb +4 -4
  58. data/spec/extensions/single_table_inheritance_spec.rb +19 -11
  59. data/spec/integration/dataset_test.rb +44 -1
  60. data/spec/integration/plugin_test.rb +39 -0
  61. data/spec/integration/prepared_statement_test.rb +58 -7
  62. data/spec/integration/spec_helper.rb +4 -0
  63. data/spec/model/eager_loading_spec.rb +24 -0
  64. data/spec/model/validations_spec.rb +5 -1
  65. metadata +114 -106
@@ -0,0 +1,84 @@
1
+ # A ShardedSingleConnectionPool is a single threaded connection pool that
2
+ # works with multiple shards/servers.
3
+ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
4
+ # Initializes the instance with the supplied block as the connection_proc.
5
+ #
6
+ # The single threaded pool takes the following options:
7
+ #
8
+ # * :servers - A hash of servers to use. Keys should be symbols. If not
9
+ # present, will use a single :default server. The server name symbol will
10
+ # be passed to the connection_proc.
11
+ def initialize(opts={}, &block)
12
+ super
13
+ @conns = {}
14
+ @servers = Hash.new(:default)
15
+ add_servers([:default])
16
+ add_servers(opts[:servers].keys) if opts[:servers]
17
+ end
18
+
19
+ # Adds new servers to the connection pool. Primarily used in conjunction with master/slave
20
+ # or shard configurations. Allows for dynamic expansion of the potential slaves/shards
21
+ # at runtime. servers argument should be an array of symbols.
22
+ def add_servers(servers)
23
+ servers.each{|s| @servers[s] = s}
24
+ end
25
+
26
+ # The connection for the given server.
27
+ def conn(server=:default)
28
+ @conns[@servers[server]]
29
+ end
30
+
31
+ # Disconnects from the database. Once a connection is requested using
32
+ # #hold, the connection is reestablished. Options:
33
+ # * :server - Should be a symbol specifing the server to disconnect from,
34
+ # or an array of symbols to specify multiple servers.
35
+ def disconnect(opts={}, &block)
36
+ block ||= @disconnection_proc
37
+ (opts[:server] ? Array(opts[:server]) : servers).each{|s| disconnect_server(s, &block)}
38
+ end
39
+
40
+ # Yields the connection to the supplied block for the given server.
41
+ # This method simulates the ConnectionPool#hold API.
42
+ def hold(server=:default)
43
+ begin
44
+ server = @servers[server]
45
+ yield(@conns[server] ||= make_new(server))
46
+ rescue Sequel::DatabaseDisconnectError
47
+ disconnect_server(server, &@disconnection_proc)
48
+ raise
49
+ end
50
+ end
51
+
52
+ # Remove servers from the connection pool. Primarily used in conjunction with master/slave
53
+ # or shard configurations. Similar to disconnecting from all given servers,
54
+ # except that after it is used, future requests for the server will use the
55
+ # :default server instead.
56
+ def remove_servers(servers)
57
+ raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
58
+ servers.each do |server|
59
+ disconnect_server(server, &@disconnection_proc)
60
+ @servers.delete(server)
61
+ end
62
+ end
63
+
64
+ # Return an array of symbols for servers in the connection pool.
65
+ def servers
66
+ @servers.keys
67
+ end
68
+
69
+ # The number of different shards/servers this pool is connected to.
70
+ def size
71
+ @conns.length
72
+ end
73
+
74
+ private
75
+
76
+ # Disconnect from the given server, if connected.
77
+ def disconnect_server(server, &block)
78
+ if conn = @conns.delete(server)
79
+ block.call(conn) if block
80
+ end
81
+ end
82
+
83
+ CONNECTION_POOL_MAP[[true, true]] = self
84
+ end
@@ -0,0 +1,211 @@
1
+ Sequel.require 'connection_pool/threaded'
2
+
3
+ # The slowest and most advanced connection, dealing with both multi-threaded
4
+ # access and configurations with multiple shards/servers.
5
+ #
6
+ # In addition, this pool subclass also handles scheduling in-use connections
7
+ # to be removed from the pool when they are returned to it.
8
+ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
9
+ # The following additional options are respected:
10
+ # * :servers - A hash of servers to use. Keys should be symbols. If not
11
+ # present, will use a single :default server. The server name symbol will
12
+ # be passed to the connection_proc.
13
+ def initialize(opts = {}, &block)
14
+ super
15
+ @available_connections = {}
16
+ @connections_to_remove = []
17
+ @servers = Hash.new(:default)
18
+ add_servers([:default])
19
+ add_servers(opts[:servers].keys) if opts[:servers]
20
+ end
21
+
22
+ # Adds new servers to the connection pool. Primarily used in conjunction with master/slave
23
+ # or shard configurations. Allows for dynamic expansion of the potential slaves/shards
24
+ # at runtime. servers argument should be an array of symbols.
25
+ def add_servers(servers)
26
+ sync do
27
+ servers.each do |server|
28
+ unless @servers.has_key?(server)
29
+ @servers[server] = server
30
+ @available_connections[server] = []
31
+ @allocated[server] = {}
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # A hash of connections currently being used for the given server, key is the
38
+ # Thread, value is the connection. Nonexistent servers will return nil. Treat
39
+ # this as read only, do not modify the resulting object.
40
+ def allocated(server=:default)
41
+ @allocated[server]
42
+ end
43
+
44
+ # An array of connections opened but not currently used, for the given
45
+ # server. Nonexistent servers will return nil. Treat this as read only, do
46
+ # not modify the resulting object.
47
+ def available_connections(server=:default)
48
+ @available_connections[server]
49
+ end
50
+
51
+ # The total number of connections opened for the given server, should
52
+ # be equal to available_connections.length + allocated.length. Nonexistent
53
+ # servers will return the created count of the default server.
54
+ def size(server=:default)
55
+ server = @servers[server]
56
+ @allocated[server].length + @available_connections[server].length
57
+ end
58
+
59
+ # Removes all connection currently available on all servers, optionally
60
+ # yielding each connection to the given block. This method has the effect of
61
+ # disconnecting from the database, assuming that no connections are currently
62
+ # being used. If connections are being used, they are scheduled to be
63
+ # disconnected as soon as they are returned to the pool.
64
+ #
65
+ # Once a connection is requested using #hold, the connection pool
66
+ # creates new connections to the database. Options:
67
+ # * :server - Should be a symbol specifing the server to disconnect from,
68
+ # or an array of symbols to specify multiple servers.
69
+ def disconnect(opts={}, &block)
70
+ block ||= @disconnection_proc
71
+ sync do
72
+ (opts[:server] ? Array(opts[:server]) : @servers.keys).each do |s|
73
+ disconnect_server(s, &block)
74
+ end
75
+ end
76
+ end
77
+
78
+ # Chooses the first available connection to the given server, or if none are
79
+ # available, creates a new connection. Passes the connection to the supplied
80
+ # block:
81
+ #
82
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
83
+ #
84
+ # Pool#hold is re-entrant, meaning it can be called recursively in
85
+ # the same thread without blocking.
86
+ #
87
+ # If no connection is immediately available and the pool is already using the maximum
88
+ # number of connections, Pool#hold will block until a connection
89
+ # is available or the timeout expires. If the timeout expires before a
90
+ # connection can be acquired, a Sequel::PoolTimeout is
91
+ # raised.
92
+ def hold(server=:default)
93
+ sync{server = @servers[server]}
94
+ t = Thread.current
95
+ if conn = owned_connection(t, server)
96
+ return yield(conn)
97
+ end
98
+ begin
99
+ unless conn = acquire(t, server)
100
+ time = Time.now
101
+ timeout = time + @timeout
102
+ sleep_time = @sleep_time
103
+ sleep sleep_time
104
+ until conn = acquire(t, server)
105
+ raise(::Sequel::PoolTimeout) if Time.now > timeout
106
+ sleep sleep_time
107
+ end
108
+ end
109
+ yield conn
110
+ rescue Sequel::DatabaseDisconnectError
111
+ sync{@connections_to_remove << conn} if conn
112
+ raise
113
+ ensure
114
+ sync{release(t, conn, server)} if conn
115
+ end
116
+ end
117
+
118
+ # Remove servers from the connection pool. Primarily used in conjunction with master/slave
119
+ # or shard configurations. Similar to disconnecting from all given servers,
120
+ # except that after it is used, future requests for the server will use the
121
+ # :default server instead.
122
+ def remove_servers(servers)
123
+ sync do
124
+ raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
125
+ servers.each do |server|
126
+ if @servers.include?(server)
127
+ disconnect_server(server, &@disconnection_proc)
128
+ @available_connections.delete(server)
129
+ @allocated.delete(server)
130
+ @servers.delete(server)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ # Return an array of symbols for servers in the connection pool.
137
+ def servers
138
+ sync{@servers.keys}
139
+ end
140
+
141
+ private
142
+
143
+ # Assigns a connection to the supplied thread for the given server, if one
144
+ # is available. The calling code should NOT already have the mutex when
145
+ # calling this.
146
+ def acquire(thread, server)
147
+ sync do
148
+ if conn = available(server)
149
+ allocated(server)[thread] = conn
150
+ end
151
+ end
152
+ end
153
+
154
+ # Returns an available connection to the given server. If no connection is
155
+ # available, tries to create a new connection. The calling code should already
156
+ # have the mutex before calling this.
157
+ def available(server)
158
+ available_connections(server).pop || make_new(server)
159
+ end
160
+
161
+ # Disconnect from the given server. Disconnects available connections
162
+ # immediately, and schedules currently allocated connections for disconnection
163
+ # as soon as they are returned to the pool. The calling code should already
164
+ # have the mutex before calling this.
165
+ def disconnect_server(server, &block)
166
+ if conns = available_connections(server)
167
+ conns.each{|conn| block.call(conn)} if block
168
+ conns.clear
169
+ end
170
+ @connections_to_remove.concat(allocated(server).values)
171
+ end
172
+
173
+ # Creates a new connection to the given server if the size of the pool for
174
+ # the server is less than the maximum size of the pool. The calling code
175
+ # should already have the mutex before calling this.
176
+ def make_new(server)
177
+ if (n = size(server)) >= @max_size
178
+ allocated(server).to_a.each{|t, c| release(t, c, server) unless t.alive?}
179
+ n = nil
180
+ end
181
+ default_make_new(server) if (n || size(server)) < @max_size
182
+ end
183
+
184
+ # Returns the connection owned by the supplied thread for the given server,
185
+ # if any. The calling code should NOT already have the mutex before calling this.
186
+ def owned_connection(thread, server)
187
+ sync{@allocated[server][thread]}
188
+ end
189
+
190
+ # Releases the connection assigned to the supplied thread and server. If the
191
+ # server or connection given is scheduled for disconnection, remove the
192
+ # connection instead of releasing it back to the pool.
193
+ # The calling code should already have the mutex before calling this.
194
+ def release(thread, conn, server)
195
+ if @connections_to_remove.include?(conn)
196
+ remove(thread, conn, server)
197
+ else
198
+ available_connections(server) << allocated(server).delete(thread)
199
+ end
200
+ end
201
+
202
+ # Removes the currently allocated connection from the connection pool. The
203
+ # calling code should already have the mutex before calling this.
204
+ def remove(thread, conn, server)
205
+ @connections_to_remove.delete(conn)
206
+ allocated(server).delete(thread) if @servers.include?(server)
207
+ @disconnection_proc.call(conn) if @disconnection_proc
208
+ end
209
+
210
+ CONNECTION_POOL_MAP[[false, true]] = self
211
+ end
@@ -0,0 +1,29 @@
1
+ # This is the fastest connection pool, since it isn't a connection pool at all.
2
+ # It is just a wrapper around a single connection that uses the connection pool
3
+ # API.
4
+ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
5
+ # The SingleConnectionPool always has a size of 1, since the connection
6
+ # is always available.
7
+ def size
8
+ @conn ? 1 : 0
9
+ end
10
+
11
+ # Disconnect and immediately reconnect from the database.
12
+ def disconnect(opts=nil, &block)
13
+ block ||= @disconnection_proc
14
+ block.call(@conn) if block
15
+ @conn = nil
16
+ end
17
+
18
+ # Yield the connection to the block.
19
+ def hold(server=nil)
20
+ begin
21
+ yield(@conn ||= make_new(DEFAULT_SERVER))
22
+ rescue Sequel::DatabaseDisconnectError
23
+ disconnect
24
+ raise
25
+ end
26
+ end
27
+
28
+ CONNECTION_POOL_MAP[[true, false]] = self
29
+ end
@@ -0,0 +1,150 @@
1
+ # A connection pool allowing multi-threaded access to a pool of connections.
2
+ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
3
+ # The maximum number of connections this pool will create (per shard/server
4
+ # if sharding).
5
+ attr_reader :max_size
6
+
7
+ # An array of connections that are available for use by the pool.
8
+ attr_reader :available_connections
9
+
10
+ # A hash with thread keys and connection values for currently allocated
11
+ # connections.
12
+ attr_reader :allocated
13
+
14
+ # The following additional options are respected:
15
+ # * :max_connections - The maximum number of connections the connection pool
16
+ # will open (default 4)
17
+ # * :pool_sleep_time - The amount of time to sleep before attempting to acquire
18
+ # a connection again (default 0.001)
19
+ # * :pool_timeout - The amount of seconds to wait to acquire a connection
20
+ # before raising a PoolTimeoutError (default 5)
21
+ def initialize(opts = {}, &block)
22
+ super
23
+ @max_size = Integer(opts[:max_connections] || 4)
24
+ raise(Sequel::Error, ':max_connections must be positive') if @max_size < 1
25
+ @mutex = Mutex.new
26
+ @available_connections = []
27
+ @allocated = {}
28
+ @timeout = Integer(opts[:pool_timeout] || 5)
29
+ @sleep_time = Float(opts[:pool_sleep_time] || 0.001)
30
+ end
31
+
32
+ # The total number of connections opened for the given server, should
33
+ # be equal to available_connections.length + allocated.length.
34
+ def size
35
+ @allocated.length + @available_connections.length
36
+ end
37
+
38
+ # Removes all connection currently available on all servers, optionally
39
+ # yielding each connection to the given block. This method has the effect of
40
+ # disconnecting from the database, assuming that no connections are currently
41
+ # being used. If connections are being used, they are scheduled to be
42
+ # disconnected as soon as they are returned to the pool.
43
+ #
44
+ # Once a connection is requested using #hold, the connection pool
45
+ # creates new connections to the database.
46
+ def disconnect(opts={}, &block)
47
+ block ||= @disconnection_proc
48
+ sync do
49
+ @available_connections.each{|conn| block.call(conn)} if block
50
+ @available_connections.clear
51
+ end
52
+ end
53
+
54
+ # Chooses the first available connection to the given server, or if none are
55
+ # available, creates a new connection. Passes the connection to the supplied
56
+ # block:
57
+ #
58
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
59
+ #
60
+ # Pool#hold is re-entrant, meaning it can be called recursively in
61
+ # the same thread without blocking.
62
+ #
63
+ # If no connection is immediately available and the pool is already using the maximum
64
+ # number of connections, Pool#hold will block until a connection
65
+ # is available or the timeout expires. If the timeout expires before a
66
+ # connection can be acquired, a Sequel::PoolTimeout is
67
+ # raised.
68
+ def hold(server=nil)
69
+ t = Thread.current
70
+ if conn = owned_connection(t)
71
+ return yield(conn)
72
+ end
73
+ begin
74
+ unless conn = acquire(t)
75
+ time = Time.now
76
+ timeout = time + @timeout
77
+ sleep_time = @sleep_time
78
+ sleep sleep_time
79
+ until conn = acquire(t)
80
+ raise(::Sequel::PoolTimeout) if Time.now > timeout
81
+ sleep sleep_time
82
+ end
83
+ end
84
+ yield conn
85
+ rescue Sequel::DatabaseDisconnectError
86
+ @disconnection_proc.call(conn) if @disconnection_proc && conn
87
+ @allocated.delete(t)
88
+ conn = nil
89
+ raise
90
+ ensure
91
+ sync{release(t)} if conn
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ # Assigns a connection to the supplied thread for the given server, if one
98
+ # is available. The calling code should NOT already have the mutex when
99
+ # calling this.
100
+ def acquire(thread)
101
+ sync do
102
+ if conn = available
103
+ @allocated[thread] = conn
104
+ end
105
+ end
106
+ end
107
+
108
+ # Returns an available connection to the given server. If no connection is
109
+ # available, tries to create a new connection. The calling code should already
110
+ # have the mutex before calling this.
111
+ def available
112
+ @available_connections.pop || make_new(DEFAULT_SERVER)
113
+ end
114
+
115
+ # Alias the default make_new method, so subclasses can call it directly.
116
+ alias default_make_new make_new
117
+
118
+ # Creates a new connection to the given server if the size of the pool for
119
+ # the server is less than the maximum size of the pool. The calling code
120
+ # should already have the mutex before calling this.
121
+ def make_new(server)
122
+ if (n = size) >= @max_size
123
+ @allocated.keys.each{|t| release(t) unless t.alive?}
124
+ n = nil
125
+ end
126
+ super if (n || size) < @max_size
127
+ end
128
+
129
+ # Returns the connection owned by the supplied thread for the given server,
130
+ # if any. The calling code should NOT already have the mutex before calling this.
131
+ def owned_connection(thread)
132
+ sync{@allocated[thread]}
133
+ end
134
+
135
+ # Releases the connection assigned to the supplied thread and server. If the
136
+ # server or connection given is scheduled for disconnection, remove the
137
+ # connection instead of releasing it back to the pool.
138
+ # The calling code should already have the mutex before calling this.
139
+ def release(thread)
140
+ @available_connections << @allocated.delete(thread)
141
+ end
142
+
143
+ # Yield to the block while inside the mutex. The calling code should NOT
144
+ # already have the mutex before calling this.
145
+ def sync
146
+ @mutex.synchronize{yield}
147
+ end
148
+
149
+ CONNECTION_POOL_MAP[[false, false]] = self
150
+ end