sequel 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
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