sequel 3.33.0 → 3.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. metadata +35 -7
@@ -0,0 +1,259 @@
1
+ # The pg_hstore_ops extension adds support to Sequel's DSL to make
2
+ # it easier to call PostgreSQL hstore functions and operators. The
3
+ # most common usage is taking an object that represents an SQL
4
+ # expression (such as a :symbol), and calling #hstore on it:
5
+ #
6
+ # h = :hstore_column.hstore
7
+ #
8
+ # This creates a Sequel::Postgres::HStoreOp object that can be used
9
+ # for easier querying:
10
+ #
11
+ # h - 'a' # hstore_column - 'a'
12
+ # h['a'] # hstore_column -> 'a'
13
+ #
14
+ # h.concat(:other_hstore_column) # ||
15
+ # h.has_key?('a') # ?
16
+ # h.contain_all(:array_column) # ?&
17
+ # h.contain_any(:array_column) # ?|
18
+ # h.contains(:other_hstore_column) # @>
19
+ # h.contained_by(:other_hstore_column) # <@
20
+ #
21
+ # h.defined # defined(hstore_column)
22
+ # h.delete('a') # delete(hstore_column, 'a')
23
+ # h.each # each(hstore_column)
24
+ # h.keys # akeys(hstore_column)
25
+ # h.populate(:a) # populate_record(a, hstore_column)
26
+ # h.record_set(:a) # (a #= hstore_column)
27
+ # h.skeys # skeys(hstore_column)
28
+ # h.slice(:a) # slice(hstore_column, a)
29
+ # h.svals # svals(hstore_column)
30
+ # h.to_array # hstore_to_array(hstore_column)
31
+ # h.to_matrix # hstore_to_matrix(hstore_column)
32
+ # h.values # avals(hstore_column)
33
+ #
34
+ # See the PostgreSQL hstore function and operator documentation for more
35
+ # details on what these functions and operators do.
36
+ #
37
+ # If you are also using the pg_hstore extension, you should load it before
38
+ # loading this extension. Doing so will allow you to use HStore#op to get
39
+ # an HStoreOp, allowing you to perform hstore operations on hstore literals.
40
+
41
+ module Sequel
42
+ module Postgres
43
+ # The HStoreOp class is a simple container for a single object that
44
+ # defines methods that yield Sequel expression objects representing
45
+ # PostgreSQL hstore operators and functions.
46
+ #
47
+ # In the method documentation examples, assume that:
48
+ #
49
+ # hstore_op = :hstore.hstore
50
+ class HStoreOp < Sequel::SQL::Wrapper
51
+ CONCAT = ["(".freeze, " || ".freeze, ")".freeze].freeze
52
+ CONTAIN_ALL = ["(".freeze, " ?& ".freeze, ")".freeze].freeze
53
+ CONTAIN_ANY = ["(".freeze, " ?| ".freeze, ")".freeze].freeze
54
+ CONTAINS = ["(".freeze, " @> ".freeze, ")".freeze].freeze
55
+ CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
56
+ HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
57
+ LOOKUP = ["(".freeze, " -> ".freeze, ")".freeze].freeze
58
+ RECORD_SET = ["(".freeze, " #= ".freeze, ")".freeze].freeze
59
+
60
+ # Delete entries from an hstore using the subtraction operator:
61
+ #
62
+ # hstore_op - 'a' # (hstore - 'a')
63
+ def -(other)
64
+ HStoreOp.new(super)
65
+ end
66
+
67
+ # Lookup the value for the given key in an hstore:
68
+ #
69
+ # hstore_op['a'] # (hstore -> 'a')
70
+ def [](key)
71
+ Sequel::SQL::StringExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, key]))
72
+ end
73
+
74
+ # Check if the receiver contains all of the keys in the given array:
75
+ #
76
+ # hstore_op.contain_all(:a) # (hstore ?& a)
77
+ def contain_all(other)
78
+ bool_op(CONTAIN_ALL, other)
79
+ end
80
+
81
+ # Check if the receiver contains any of the keys in the given array:
82
+ #
83
+ # hstore_op.contain_any(:a) # (hstore ?| a)
84
+ def contain_any(other)
85
+ bool_op(CONTAIN_ANY, other)
86
+ end
87
+
88
+ # Check if the receiver contains all entries in the other hstore:
89
+ #
90
+ # hstore_op.contains(:h) # (hstore @> h)
91
+ def contains(other)
92
+ bool_op(CONTAINS, other)
93
+ end
94
+
95
+ # Check if the other hstore contains all entries in the receiver:
96
+ #
97
+ # hstore_op.contained_by(:h) # (hstore <@ h)
98
+ def contained_by(other)
99
+ bool_op(CONTAINED_BY, other)
100
+ end
101
+
102
+ # Check if the receiver contains a non-NULL value for the given key:
103
+ #
104
+ # hstore_op.defined('a') # defined(hstore, 'a')
105
+ def defined(key)
106
+ Sequel::SQL::BooleanExpression.new(:NOOP, function(:defined, key))
107
+ end
108
+
109
+ # Delete the matching entries from the receiver:
110
+ #
111
+ # hstore_op.delete('a') # delete(hstore, 'a')
112
+ def delete(key)
113
+ HStoreOp.new(function(:delete, key))
114
+ end
115
+
116
+ # Transform the receiver into a set of keys and values:
117
+ #
118
+ # hstore_op.each # each(hstore)
119
+ def each
120
+ function(:each)
121
+ end
122
+
123
+ # Check if the receiver contains the given key:
124
+ #
125
+ # hstore_op.has_key?('a') # (hstore ? 'a')
126
+ def has_key?(key)
127
+ bool_op(HAS_KEY, key)
128
+ end
129
+ alias include? has_key?
130
+ alias key? has_key?
131
+ alias member? has_key?
132
+ alias exist? has_key?
133
+
134
+ # Return the receiver.
135
+ def hstore
136
+ self
137
+ end
138
+
139
+ # Return the keys as a PostgreSQL array:
140
+ #
141
+ # hstore_op.keys # akeys(hstore)
142
+ def keys
143
+ function(:akeys)
144
+ end
145
+ alias akeys keys
146
+
147
+ # Merge a given hstore into the receiver:
148
+ #
149
+ # hstore_op.merge(:a) # (hstore || a)
150
+ def merge(other)
151
+ HStoreOp.new(Sequel::SQL::PlaceholderLiteralString.new(CONCAT, [self, other]))
152
+ end
153
+ alias concat merge
154
+
155
+ # Create a new record populated with entries from the receiver:
156
+ #
157
+ # hstore_op.populate(:a) # populate_record(a, hstore)
158
+ def populate(record)
159
+ SQL::Function.new(:populate_record, record, self)
160
+ end
161
+
162
+ # Update the values in a record using entries in the receiver:
163
+ #
164
+ # hstore_op.record_set(:a) # (a #= hstore)
165
+ def record_set(record)
166
+ Sequel::SQL::PlaceholderLiteralString.new(RECORD_SET, [record, value])
167
+ end
168
+
169
+ # Return the keys as a PostgreSQL set:
170
+ #
171
+ # hstore_op.skeys # skeys(hstore)
172
+ def skeys
173
+ function(:skeys)
174
+ end
175
+
176
+ # Return an hstore with only the keys in the given array:
177
+ #
178
+ # hstore_op.slice(:a) # slice(hstore, a)
179
+ def slice(keys)
180
+ HStoreOp.new(function(:slice, keys))
181
+ end
182
+
183
+ # Return the values as a PostgreSQL set:
184
+ #
185
+ # hstore_op.svals # svals(hstore)
186
+ def svals
187
+ function(:svals)
188
+ end
189
+
190
+ # Return a flattened array of the receiver with alternating
191
+ # keys and values:
192
+ #
193
+ # hstore_op.to_array # hstore_to_array(hstore)
194
+ def to_array
195
+ function(:hstore_to_array)
196
+ end
197
+
198
+ # Return a nested array of the receiver, with arrays of
199
+ # 2 element (key/value) arrays:
200
+ #
201
+ # hstore_op.to_matrix # hstore_to_matrix(hstore)
202
+ def to_matrix
203
+ function(:hstore_to_matrix)
204
+ end
205
+
206
+ # Return the values as a PostgreSQL array:
207
+ #
208
+ # hstore_op.values # avals(hstore)
209
+ def values
210
+ function(:avals)
211
+ end
212
+ alias avals values
213
+
214
+ private
215
+
216
+ # Return a placeholder literal with the given str and args, wrapped
217
+ # in a boolean expression, used by operators that return booleans.
218
+ def bool_op(str, other)
219
+ Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
220
+ end
221
+
222
+ # Return a function with the given name, and the receiver as the first
223
+ # argument, with any additional arguments given.
224
+ def function(name, *args)
225
+ SQL::Function.new(name, self, *args)
226
+ end
227
+ end
228
+
229
+ module HStoreOpMethods
230
+ # Wrap the receiver in an HStoreOp so you can easily use the PostgreSQL
231
+ # hstore functions and operators with it.
232
+ def hstore
233
+ HStoreOp.new(self)
234
+ end
235
+ end
236
+
237
+ if defined?(HStore)
238
+ class HStore
239
+ # Wrap the receiver in an HStoreOp so you can easily use the PostgreSQL
240
+ # hstore functions and operators with it.
241
+ def op
242
+ HStoreOp.new(self)
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ class SQL::GenericExpression
249
+ include Sequel::Postgres::HStoreOpMethods
250
+ end
251
+
252
+ class LiteralString
253
+ include Sequel::Postgres::HStoreOpMethods
254
+ end
255
+ end
256
+
257
+ class Symbol
258
+ include Sequel::Postgres::HStoreOpMethods
259
+ end
@@ -0,0 +1,316 @@
1
+ # This extension adds a statement cache to Sequel's postgres adapter,
2
+ # with the ability to automatically prepare statements that are
3
+ # executed repeatedly. When combined with the pg_auto_parameterize
4
+ # extension, it can take Sequel code such as:
5
+ #
6
+ # DB.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
7
+ # DB.extend Sequel::Postgres::StatementCache::DatabaseMethods
8
+ # DB[:table].filter(:a=>1)
9
+ # DB[:table].filter(:a=>2)
10
+ # DB[:table].filter(:a=>3)
11
+ #
12
+ # And use the same prepared statement to execute the queries.
13
+ #
14
+ # The backbone of this extension is a modified LRU cache. It considers
15
+ # both the last executed time and the number of executions when
16
+ # determining which queries to keep in the cache. It only cleans the
17
+ # cache when a high water mark has been passed, and removes queries
18
+ # until it reaches the low water mark, in order to avoid thrashing when
19
+ # you are using more than the maximum number of queries. To avoid
20
+ # preparing queries when it isn't necessary, it does not prepare them
21
+ # on the server side unless they are being executed more than once.
22
+ # The cache is very tunable, allowing you to set the high and low
23
+ # water marks, the number of executions before preparing the query,
24
+ # and even use a custom callback for determine which queries to keep
25
+ # in the cache.
26
+ #
27
+ # Note that automatically preparing statements does have some issues.
28
+ # Most notably, if you change the result type that the query returns,
29
+ # PostgreSQL will raise an error. This can happen if you have
30
+ # prepared a statement that selects all columns from a table, and then
31
+ # you add or remove a column from that table. This extension does
32
+ # attempt to check that case and clear the statement caches if you use
33
+ # alter_table from within Sequel, but it cannot fix the case when such
34
+ # a change is made externally.
35
+ #
36
+ # This extension only works when the pg driver is used as the backend
37
+ # for the postgres adapter.
38
+
39
+ module Sequel
40
+ module Postgres
41
+ module StatementCache
42
+ # A simple structure used for the values in the StatementCache's hash.
43
+ # It does not hold the related SQL, since that is used as the key for
44
+ # the StatementCache's hash.
45
+ class Statement
46
+ # The last time this statement was seen by the cache, persumably the
47
+ # last time it was executed.
48
+ attr_accessor :last_seen
49
+
50
+ # The total number of executions since the statement entered the cache.
51
+ attr_accessor :num_executes
52
+
53
+ # The id related to the statement, used as part of the prepared statement
54
+ # name.
55
+ attr_reader :cache_id
56
+
57
+ # Used when adding entries to the cache, just sets their id. Uses
58
+ # 0 for num_executes since that is incremented elsewhere. Does not
59
+ # set last_seen since that is set elsewhere to reduce branching.
60
+ def initialize(cache_id)
61
+ @num_executes = 0
62
+ @cache_id = cache_id
63
+ end
64
+
65
+ # The name to use for the server side prepared statement. Note that this
66
+ # statement might not actually be prepared.
67
+ def name
68
+ "sequel_pgap_#{cache_id}"
69
+ end
70
+ end
71
+
72
+ # The backbone of the block, a modified LRU (least recently used) cache
73
+ # mapping SQL query strings to Statement objects.
74
+ class StatementCache
75
+ include Enumerable
76
+
77
+ # Set the options for the statement cache. These are generally set at
78
+ # the database level using the :statement_cache_opts Database option.
79
+ #
80
+ # :max_size :: The maximum size (high water mark) for the cache. If
81
+ # an entry is added when the current size of the cache is
82
+ # equal to the maximum size, the cache is cleaned up to
83
+ # reduce the number of entries to the :min_size. Defaults
84
+ # to 1000.
85
+ # :min_size :: The minimum size (low water mark) for the cache. On
86
+ # cleanup, the size of the cache is reduced to this
87
+ # number. Note that there could be fewer than this
88
+ # number of entries in the cache. Defaults to :max_size/2.
89
+ # :prepare_after :: The number of executions to wait for before preparing
90
+ # the query server-side. If set to 1, prepares all executed
91
+ # queries server-side. If set to 5, does not attempt to
92
+ # prepare the query until the 5th execution. Defaults to 2.
93
+ # :sorter :: A callable object that takes two arguments, the current time
94
+ # and the related Statement instance, and should return some
95
+ # Comparable (usually a numeric) such that the lowest values
96
+ # returned are the first to be removed when it comes time to
97
+ # clean the pool. The default is basically:
98
+ #
99
+ # lambda{|t, stmt| (stmt.last_seen - t)/stmt.num_executes}
100
+ #
101
+ # so that it doesn't remove statements that have been executed
102
+ # many times just because many less-frequently executed statements
103
+ # have been executed recently.
104
+ #
105
+ # The block passed is called with the Statement object's name, only for
106
+ # statements that have been prepared, and should be used to deallocate the
107
+ # statements.
108
+ def initialize(opts={}, &block)
109
+ @cleanup_proc = block
110
+ @prepare_after = opts.fetch(:prepare_after, 2)
111
+ @max_size = opts.fetch(:max_size, 1000)
112
+ @min_size = opts.fetch(:min_size, @max_size/2)
113
+ @sorter = opts.fetch(:sorter){method(:default_sorter)}
114
+ @ids = (1..@max_size).to_a.reverse
115
+ @hash = {}
116
+ #
117
+ # We add one so that when we clean the cache, the entry
118
+ # about to be added brings us to the min_size.
119
+ @size_diff = @max_size - @min_size + 1
120
+ end
121
+
122
+ # Completely clear the statement cache, deallocating on
123
+ # the server side all statements that have been prepared.
124
+ def clear
125
+ @hash.keys.each{|k| remove(k)}
126
+ end
127
+
128
+ # Yield each SQL string and Statement instance in the cache
129
+ # to the block.
130
+ def each(&block)
131
+ @hash.each(&block)
132
+ end
133
+
134
+ # Get the related statement name from the cache. If the
135
+ # entry is already in the cache, just bump it's last seen
136
+ # time and the number of executions. Otherwise, add it
137
+ # to the cache. If the cache is already full, clean it up
138
+ # before adding it.
139
+ #
140
+ # If the num of executions has passed the threshhold, yield
141
+ # the statement name to the block, which should be used to
142
+ # prepare the statement on the server side.
143
+ #
144
+ # This method should return the prepared statment name if
145
+ # the statement has been prepared, and nil if the query
146
+ # has not been prepared and the statement should be executed
147
+ # normally.
148
+ def fetch(sql)
149
+ unless stmt = @hash[sql]
150
+ # Get the next id from the id pool.
151
+ unless id = @ids.pop
152
+ # No id left, cache must be full, so cleanup and then
153
+ # get the next id from the id pool.
154
+ cleanup
155
+ id = @ids.pop
156
+ end
157
+ @hash[sql] = stmt = Statement.new(id)
158
+ end
159
+
160
+ stmt.last_seen = Time.now
161
+ stmt.num_executes += 1
162
+
163
+ if stmt.num_executes >= @prepare_after
164
+ if stmt.num_executes == @prepare_after
165
+ begin
166
+ yield(stmt.name)
167
+ rescue PGError
168
+ # An error occurred while preparing the statement,
169
+ # execute it normally (which will probably raise
170
+ # the error again elsewhere), but decrement the
171
+ # number of executions so we don't think we've
172
+ # prepared the statement when we haven't.
173
+ stmt.num_executes -= 1
174
+ return nil
175
+ end
176
+ end
177
+ stmt.name
178
+ end
179
+ end
180
+
181
+ # The current size of the statement cache.
182
+ def size
183
+ @hash.length
184
+ end
185
+
186
+ private
187
+
188
+ # Sort by time since last execution and number of executions.
189
+ # We don't want to throw stuff out of the
190
+ # cache if it has been executed a lot,
191
+ # but a bunch of queries that were
192
+ # executed only once came in more recently.
193
+ def default_sorter(t, stmt)
194
+ (stmt.last_seen - t)/stmt.num_executes
195
+ end
196
+
197
+ # After sorting the cache appropriately (so that the least important
198
+ # items are first), reduce the number of entries in the cache to
199
+ # the low water mark by removing the related query. Should only be
200
+ # called when the cache is full.
201
+ def cleanup
202
+ t = Time.now
203
+ @hash.sort_by{|k,v| @sorter.call(t, v)}.first(@size_diff).each{|sql, stmt| remove(sql)}
204
+ end
205
+
206
+ # Remove the query from the cache. If it has been prepared,
207
+ # call the cleanup_proc to deallocate the statement.
208
+ def remove(sql)
209
+ stmt = @hash.delete(sql)
210
+ if stmt.num_executes >= @prepare_after
211
+ @cleanup_proc.call(stmt.name)
212
+ end
213
+
214
+ # Return id to the pool of ids
215
+ @ids.push(stmt.cache_id)
216
+ end
217
+ end
218
+
219
+ module AdapterMethods
220
+ # A regular expression for the types of queries to cache. Any queries not
221
+ # matching this regular expression are not cached.
222
+ DML_RE = /\A(WITH|SELECT|INSERT|UPDATE|DELETE) /
223
+
224
+ # The StatementCache instance for this connection. Note that
225
+ # each connection has a separate StatementCache, because prepared
226
+ # statements are connection-specific.
227
+ attr_reader :statement_cache
228
+
229
+ # Set the statement_cache for the connection, using the database's
230
+ # :statement_cache_opts option.
231
+ def self.extended(c)
232
+ c.instance_variable_set(:@statement_cache, StatementCache.new(c.sequel_db.opts[:statement_cache_opts] || {}){|name| c.deallocate(name)})
233
+ end
234
+
235
+ # pg seems to already use the db method (but not the @db instance variable),
236
+ # so use the sequel_db method to access the related Sequel::Database object.
237
+ def sequel_db
238
+ @db
239
+ end
240
+
241
+ # Deallocate on the server the prepared statement with the given name.
242
+ def deallocate(name)
243
+ begin
244
+ execute("DEALLOCATE #{name}")
245
+ rescue PGError
246
+ # table probably got removed, just ignore it
247
+ end
248
+ end
249
+
250
+ private
251
+
252
+ # If the sql query string is one we should cache, cache it. If the query already
253
+ # has a related prepared statement with it, execute the prepared statement instead
254
+ # of executing the query normally.
255
+ def execute_query(sql, args=nil)
256
+ if sql =~ DML_RE
257
+ if name = statement_cache.fetch(sql){|stmt_name| sequel_db.log_yield("PREPARE #{stmt_name} AS #{sql}"){prepare(stmt_name, sql)}}
258
+ if args
259
+ sequel_db.log_yield("EXECUTE #{name} (#{sql})", args){exec_prepared(name, args)}
260
+ else
261
+ sequel_db.log_yield("EXECUTE #{name} (#{sql})"){exec_prepared(name)}
262
+ end
263
+ else
264
+ super
265
+ end
266
+ else
267
+ super
268
+ end
269
+ end
270
+ end
271
+
272
+ module DatabaseMethods
273
+ # Setup the after_connect proc for the connection pool to make
274
+ # sure the connection object is extended with the appropriate
275
+ # module. This disconnects any existing connections to ensure
276
+ # that all connections in the pool have been extended appropriately.
277
+ def self.extended(db)
278
+ # Respect existing after_connect proc if one is present
279
+ pr = db.opts[:after_connect]
280
+
281
+ # Set the after_connect proc to extend the adapter with
282
+ # the statement cache support.
283
+ db.pool.after_connect = db.opts[:after_connect] = proc do |c|
284
+ pr.call(c) if pr
285
+ c.extend(AdapterMethods)
286
+ end
287
+
288
+ # Disconnect to make sure all connections get set up with
289
+ # statement cache.
290
+ db.disconnect
291
+ end
292
+
293
+ # Clear statement caches for all connections when altering tables.
294
+ def alter_table(*)
295
+ clear_statement_caches
296
+ super
297
+ end
298
+
299
+ # Clear statement caches for all connections when dropping tables.
300
+ def drop_table(*)
301
+ clear_statement_caches
302
+ super
303
+ end
304
+
305
+ private
306
+
307
+ # Clear the statement cache for all connections. Note that for
308
+ # the threaded pools, this will not affect connections currently
309
+ # allocated to other threads.
310
+ def clear_statement_caches
311
+ pool.all_connections{|c| c.statement_cache.clear}
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end