sequel 3.48.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +114 -0
  3. data/Rakefile +10 -7
  4. data/doc/association_basics.rdoc +25 -23
  5. data/doc/code_order.rdoc +7 -0
  6. data/doc/core_extensions.rdoc +0 -10
  7. data/doc/object_model.rdoc +4 -1
  8. data/doc/querying.rdoc +3 -3
  9. data/doc/release_notes/4.0.0.txt +262 -0
  10. data/doc/security.rdoc +0 -28
  11. data/doc/testing.rdoc +8 -14
  12. data/lib/sequel/adapters/ado.rb +7 -11
  13. data/lib/sequel/adapters/ado/access.rb +8 -8
  14. data/lib/sequel/adapters/ado/mssql.rb +4 -4
  15. data/lib/sequel/adapters/amalgalite.rb +6 -6
  16. data/lib/sequel/adapters/cubrid.rb +7 -7
  17. data/lib/sequel/adapters/db2.rb +5 -9
  18. data/lib/sequel/adapters/dbi.rb +2 -6
  19. data/lib/sequel/adapters/do.rb +4 -4
  20. data/lib/sequel/adapters/firebird.rb +4 -4
  21. data/lib/sequel/adapters/ibmdb.rb +8 -8
  22. data/lib/sequel/adapters/informix.rb +2 -10
  23. data/lib/sequel/adapters/jdbc.rb +17 -17
  24. data/lib/sequel/adapters/jdbc/as400.rb +2 -2
  25. data/lib/sequel/adapters/jdbc/cubrid.rb +1 -1
  26. data/lib/sequel/adapters/jdbc/db2.rb +1 -1
  27. data/lib/sequel/adapters/jdbc/derby.rb +1 -1
  28. data/lib/sequel/adapters/jdbc/h2.rb +2 -2
  29. data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
  30. data/lib/sequel/adapters/jdbc/informix.rb +1 -1
  31. data/lib/sequel/adapters/jdbc/mssql.rb +2 -2
  32. data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
  33. data/lib/sequel/adapters/jdbc/oracle.rb +5 -1
  34. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -3
  35. data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
  36. data/lib/sequel/adapters/jdbc/transactions.rb +3 -3
  37. data/lib/sequel/adapters/mock.rb +7 -7
  38. data/lib/sequel/adapters/mysql.rb +3 -3
  39. data/lib/sequel/adapters/mysql2.rb +4 -4
  40. data/lib/sequel/adapters/odbc.rb +2 -6
  41. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  42. data/lib/sequel/adapters/openbase.rb +1 -5
  43. data/lib/sequel/adapters/oracle.rb +13 -17
  44. data/lib/sequel/adapters/postgres.rb +20 -25
  45. data/lib/sequel/adapters/shared/cubrid.rb +3 -3
  46. data/lib/sequel/adapters/shared/db2.rb +2 -2
  47. data/lib/sequel/adapters/shared/firebird.rb +7 -7
  48. data/lib/sequel/adapters/shared/mssql.rb +9 -9
  49. data/lib/sequel/adapters/shared/mysql.rb +29 -13
  50. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +7 -7
  51. data/lib/sequel/adapters/shared/oracle.rb +22 -13
  52. data/lib/sequel/adapters/shared/postgres.rb +61 -46
  53. data/lib/sequel/adapters/shared/sqlite.rb +9 -9
  54. data/lib/sequel/adapters/sqlite.rb +17 -11
  55. data/lib/sequel/adapters/swift.rb +3 -3
  56. data/lib/sequel/adapters/swift/mysql.rb +1 -1
  57. data/lib/sequel/adapters/swift/sqlite.rb +1 -1
  58. data/lib/sequel/adapters/tinytds.rb +8 -8
  59. data/lib/sequel/ast_transformer.rb +3 -1
  60. data/lib/sequel/connection_pool.rb +4 -2
  61. data/lib/sequel/connection_pool/sharded_single.rb +2 -2
  62. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -5
  63. data/lib/sequel/connection_pool/threaded.rb +7 -7
  64. data/lib/sequel/core.rb +4 -67
  65. data/lib/sequel/database.rb +1 -0
  66. data/lib/sequel/database/connecting.rb +2 -8
  67. data/lib/sequel/database/dataset.rb +2 -7
  68. data/lib/sequel/database/dataset_defaults.rb +0 -18
  69. data/lib/sequel/database/features.rb +4 -4
  70. data/lib/sequel/database/misc.rb +6 -8
  71. data/lib/sequel/database/query.rb +5 -61
  72. data/lib/sequel/database/schema_generator.rb +22 -20
  73. data/lib/sequel/database/schema_methods.rb +48 -20
  74. data/lib/sequel/database/transactions.rb +7 -17
  75. data/lib/sequel/dataset.rb +2 -0
  76. data/lib/sequel/dataset/actions.rb +23 -91
  77. data/lib/sequel/dataset/features.rb +1 -4
  78. data/lib/sequel/dataset/graph.rb +3 -47
  79. data/lib/sequel/dataset/misc.rb +4 -33
  80. data/lib/sequel/dataset/prepared_statements.rb +3 -1
  81. data/lib/sequel/dataset/query.rb +116 -240
  82. data/lib/sequel/dataset/sql.rb +19 -97
  83. data/lib/sequel/deprecated.rb +0 -16
  84. data/lib/sequel/exceptions.rb +0 -3
  85. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  86. data/lib/sequel/extensions/columns_introspection.rb +1 -12
  87. data/lib/sequel/extensions/constraint_validations.rb +3 -3
  88. data/lib/sequel/extensions/core_extensions.rb +0 -9
  89. data/lib/sequel/extensions/date_arithmetic.rb +1 -2
  90. data/lib/sequel/extensions/graph_each.rb +11 -0
  91. data/lib/sequel/extensions/migration.rb +5 -5
  92. data/lib/sequel/extensions/null_dataset.rb +11 -13
  93. data/lib/sequel/extensions/pagination.rb +3 -6
  94. data/lib/sequel/extensions/pg_array.rb +6 -4
  95. data/lib/sequel/extensions/pg_array_ops.rb +35 -1
  96. data/lib/sequel/extensions/pg_json.rb +12 -2
  97. data/lib/sequel/extensions/pg_json_ops.rb +266 -0
  98. data/lib/sequel/extensions/pg_range.rb +2 -2
  99. data/lib/sequel/extensions/pg_range_ops.rb +0 -8
  100. data/lib/sequel/extensions/pg_row.rb +2 -2
  101. data/lib/sequel/extensions/pretty_table.rb +0 -4
  102. data/lib/sequel/extensions/query.rb +3 -8
  103. data/lib/sequel/extensions/schema_caching.rb +0 -7
  104. data/lib/sequel/extensions/schema_dumper.rb +10 -17
  105. data/lib/sequel/extensions/select_remove.rb +0 -4
  106. data/lib/sequel/extensions/set_overrides.rb +28 -0
  107. data/lib/sequel/extensions/to_dot.rb +6 -10
  108. data/lib/sequel/model.rb +6 -7
  109. data/lib/sequel/model/associations.rb +127 -182
  110. data/lib/sequel/model/base.rb +88 -211
  111. data/lib/sequel/model/errors.rb +0 -13
  112. data/lib/sequel/model/plugins.rb +2 -2
  113. data/lib/sequel/no_core_ext.rb +0 -1
  114. data/lib/sequel/plugins/after_initialize.rb +11 -17
  115. data/lib/sequel/plugins/association_autoreloading.rb +1 -47
  116. data/lib/sequel/plugins/association_dependencies.rb +2 -2
  117. data/lib/sequel/plugins/auto_validations.rb +2 -8
  118. data/lib/sequel/plugins/blacklist_security.rb +32 -2
  119. data/lib/sequel/plugins/caching.rb +1 -1
  120. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  121. data/lib/sequel/plugins/composition.rb +10 -8
  122. data/lib/sequel/plugins/constraint_validations.rb +2 -2
  123. data/lib/sequel/plugins/dataset_associations.rb +4 -0
  124. data/lib/sequel/plugins/defaults_setter.rb +8 -6
  125. data/lib/sequel/plugins/dirty.rb +6 -6
  126. data/lib/sequel/plugins/force_encoding.rb +13 -8
  127. data/lib/sequel/plugins/hook_class_methods.rb +1 -7
  128. data/lib/sequel/plugins/json_serializer.rb +13 -74
  129. data/lib/sequel/plugins/lazy_attributes.rb +2 -4
  130. data/lib/sequel/plugins/list.rb +1 -1
  131. data/lib/sequel/plugins/many_through_many.rb +4 -11
  132. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +1 -49
  133. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  134. data/lib/sequel/plugins/optimistic_locking.rb +3 -5
  135. data/lib/sequel/plugins/pg_array_associations.rb +453 -0
  136. data/lib/sequel/plugins/pg_typecast_on_load.rb +23 -7
  137. data/lib/sequel/plugins/prepared_statements.rb +1 -1
  138. data/lib/sequel/plugins/prepared_statements_associations.rb +20 -14
  139. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -2
  140. data/lib/sequel/plugins/rcte_tree.rb +1 -1
  141. data/lib/sequel/plugins/serialization.rb +5 -4
  142. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
  143. data/lib/sequel/plugins/sharding.rb +7 -1
  144. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  145. data/lib/sequel/plugins/timestamps.rb +1 -1
  146. data/lib/sequel/plugins/touch.rb +2 -2
  147. data/lib/sequel/plugins/tree.rb +1 -1
  148. data/lib/sequel/plugins/typecast_on_load.rb +19 -4
  149. data/lib/sequel/plugins/validation_class_methods.rb +0 -30
  150. data/lib/sequel/plugins/validation_helpers.rb +13 -31
  151. data/lib/sequel/plugins/xml_serializer.rb +18 -57
  152. data/lib/sequel/sql.rb +20 -22
  153. data/lib/sequel/version.rb +2 -2
  154. data/spec/adapters/db2_spec.rb +14 -23
  155. data/spec/adapters/firebird_spec.rb +25 -29
  156. data/spec/adapters/informix_spec.rb +11 -14
  157. data/spec/adapters/mssql_spec.rb +71 -77
  158. data/spec/adapters/mysql_spec.rb +165 -172
  159. data/spec/adapters/oracle_spec.rb +36 -39
  160. data/spec/adapters/postgres_spec.rb +175 -100
  161. data/spec/adapters/spec_helper.rb +13 -11
  162. data/spec/adapters/sqlite_spec.rb +36 -44
  163. data/spec/core/connection_pool_spec.rb +2 -1
  164. data/spec/core/database_spec.rb +55 -55
  165. data/spec/core/dataset_spec.rb +45 -249
  166. data/spec/core/deprecated_spec.rb +0 -8
  167. data/spec/core/expression_filters_spec.rb +23 -5
  168. data/spec/core/object_graph_spec.rb +4 -66
  169. data/spec/core/schema_spec.rb +35 -12
  170. data/spec/core/spec_helper.rb +3 -2
  171. data/spec/core_extensions_spec.rb +17 -19
  172. data/spec/extensions/arbitrary_servers_spec.rb +2 -3
  173. data/spec/extensions/association_dependencies_spec.rb +14 -14
  174. data/spec/extensions/auto_validations_spec.rb +7 -0
  175. data/spec/extensions/blacklist_security_spec.rb +5 -5
  176. data/spec/extensions/blank_spec.rb +2 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  178. data/spec/extensions/columns_introspection_spec.rb +2 -29
  179. data/spec/extensions/composition_spec.rb +10 -17
  180. data/spec/extensions/core_refinements_spec.rb +5 -1
  181. data/spec/extensions/dataset_associations_spec.rb +18 -0
  182. data/spec/extensions/date_arithmetic_spec.rb +2 -2
  183. data/spec/extensions/defaults_setter_spec.rb +9 -9
  184. data/spec/extensions/dirty_spec.rb +0 -5
  185. data/spec/extensions/eval_inspect_spec.rb +2 -0
  186. data/spec/extensions/force_encoding_spec.rb +2 -18
  187. data/spec/extensions/hash_aliases_spec.rb +8 -0
  188. data/spec/extensions/hook_class_methods_spec.rb +39 -58
  189. data/spec/extensions/inflector_spec.rb +2 -0
  190. data/spec/extensions/instance_filters_spec.rb +8 -8
  191. data/spec/extensions/json_serializer_spec.rb +1 -41
  192. data/spec/extensions/list_spec.rb +1 -1
  193. data/spec/extensions/many_through_many_spec.rb +106 -109
  194. data/spec/extensions/migration_spec.rb +2 -0
  195. data/spec/extensions/named_timezones_spec.rb +1 -0
  196. data/spec/extensions/pg_array_associations_spec.rb +603 -0
  197. data/spec/extensions/pg_array_ops_spec.rb +25 -0
  198. data/spec/extensions/pg_array_spec.rb +9 -1
  199. data/spec/extensions/pg_hstore_ops_spec.rb +13 -0
  200. data/spec/extensions/pg_hstore_spec.rb +1 -0
  201. data/spec/extensions/pg_json_ops_spec.rb +131 -0
  202. data/spec/extensions/pg_json_spec.rb +10 -4
  203. data/spec/extensions/pg_range_ops_spec.rb +2 -5
  204. data/spec/extensions/pg_range_spec.rb +6 -2
  205. data/spec/extensions/pg_row_ops_spec.rb +2 -0
  206. data/spec/extensions/prepared_statements_associations_spec.rb +26 -5
  207. data/spec/extensions/rcte_tree_spec.rb +15 -15
  208. data/spec/extensions/schema_dumper_spec.rb +0 -1
  209. data/spec/extensions/schema_spec.rb +9 -9
  210. data/spec/extensions/serialization_modification_detection_spec.rb +1 -1
  211. data/spec/extensions/serialization_spec.rb +18 -29
  212. data/spec/extensions/set_overrides_spec.rb +4 -0
  213. data/spec/extensions/{many_to_one_pk_lookup_spec.rb → shared_caching_spec.rb} +1 -4
  214. data/spec/extensions/single_table_inheritance_spec.rb +4 -4
  215. data/spec/extensions/spec_helper.rb +8 -9
  216. data/spec/extensions/sql_expr_spec.rb +2 -0
  217. data/spec/extensions/string_date_time_spec.rb +2 -0
  218. data/spec/extensions/string_stripper_spec.rb +2 -0
  219. data/spec/extensions/tactical_eager_loading_spec.rb +12 -12
  220. data/spec/extensions/thread_local_timezones_spec.rb +2 -0
  221. data/spec/extensions/timestamps_spec.rb +1 -1
  222. data/spec/extensions/to_dot_spec.rb +1 -1
  223. data/spec/extensions/touch_spec.rb +24 -24
  224. data/spec/extensions/tree_spec.rb +7 -7
  225. data/spec/extensions/typecast_on_load_spec.rb +8 -1
  226. data/spec/extensions/update_primary_key_spec.rb +10 -10
  227. data/spec/extensions/validation_class_methods_spec.rb +10 -39
  228. data/spec/extensions/validation_helpers_spec.rb +29 -47
  229. data/spec/extensions/xml_serializer_spec.rb +1 -23
  230. data/spec/integration/associations_test.rb +231 -40
  231. data/spec/integration/database_test.rb +1 -1
  232. data/spec/integration/dataset_test.rb +64 -64
  233. data/spec/integration/eager_loader_test.rb +28 -28
  234. data/spec/integration/migrator_test.rb +1 -1
  235. data/spec/integration/model_test.rb +2 -2
  236. data/spec/integration/plugin_test.rb +21 -21
  237. data/spec/integration/prepared_statement_test.rb +7 -7
  238. data/spec/integration/schema_test.rb +115 -110
  239. data/spec/integration/spec_helper.rb +17 -27
  240. data/spec/integration/timezone_test.rb +1 -1
  241. data/spec/integration/transaction_test.rb +10 -10
  242. data/spec/integration/type_test.rb +2 -2
  243. data/spec/model/association_reflection_spec.rb +2 -28
  244. data/spec/model/associations_spec.rb +239 -188
  245. data/spec/model/base_spec.rb +27 -68
  246. data/spec/model/dataset_methods_spec.rb +4 -4
  247. data/spec/model/eager_loading_spec.rb +160 -172
  248. data/spec/model/hooks_spec.rb +62 -79
  249. data/spec/model/model_spec.rb +36 -51
  250. data/spec/model/plugins_spec.rb +5 -19
  251. data/spec/model/record_spec.rb +125 -151
  252. data/spec/model/spec_helper.rb +8 -6
  253. data/spec/model/validations_spec.rb +4 -17
  254. data/spec/spec_config.rb +2 -10
  255. metadata +50 -56
  256. data/lib/sequel/deprecated_core_extensions.rb +0 -135
  257. data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -185
  258. data/lib/sequel/extensions/pg_statement_cache.rb +0 -318
  259. data/lib/sequel/plugins/identity_map.rb +0 -260
  260. data/lib/sequel_core.rb +0 -2
  261. data/lib/sequel_model.rb +0 -2
  262. data/spec/extensions/association_autoreloading_spec.rb +0 -102
  263. data/spec/extensions/identity_map_spec.rb +0 -337
  264. data/spec/extensions/pg_auto_parameterize_spec.rb +0 -70
  265. data/spec/extensions/pg_statement_cache_spec.rb +0 -208
  266. data/spec/rcov.opts +0 -8
  267. data/spec/spec_config.rb.example +0 -10
@@ -1,185 +0,0 @@
1
- # This extension allows Sequel's postgres adapter to automatically
2
- # parameterize all common queries. Sequel's default behavior has always
3
- # been to literalize all arguments unless specifically using
4
- # parameters (via :$arg placeholders and the prepare/call methods).
5
- # This extension makes Sequel take all string, numeric, date, and
6
- # time types and automatically turn them into parameters. Example:
7
- #
8
- # # Default
9
- # DB[:test].where(:a=>1)
10
- # # SQL: SELECT * FROM test WHERE a = 1
11
- #
12
- # DB.extension :pg_auto_parameterize
13
- # DB[:test].where(:a=>1)
14
- # # SQL: SELECT * FROM test WHERE a = $1 (args: [1])
15
- #
16
- # This extension is not necessarily faster or more safe than the
17
- # default behavior. In some cases it is faster, such as when using
18
- # large strings. However, there are also some known issues with
19
- # this approach:
20
- #
21
- # 1. Because of the way it operates, it has no context to make a
22
- # determination about whether to literalize an object or not.
23
- # For example, if it comes across an integer, it will turn it
24
- # into a parameter. That breaks code such as:
25
- #
26
- # DB[:table].select(:a, :b).order(2, 1)
27
- #
28
- # Since it will use the following SQL (which isn't valid):
29
- #
30
- # SELECT a, b FROM table ORDER BY $1, $2
31
- #
32
- # To work around this, you can either specify the columns
33
- # manually or use a literal string:
34
- #
35
- # DB[:table].select(:a, :b).order(:b, :a)
36
- # DB[:table].select(:a, :b).order(Sequel.lit('2, 1'))
37
- #
38
- # 2. In order to avoid many type errors, it attempts to guess the
39
- # appropriate type and automatically casts all placeholders.
40
- # Unfortunately, if the type guess is incorrect, the query will
41
- # be rejected. For example, the following works without
42
- # automatic parameterization, but fails with it:
43
- #
44
- # DB[:table].insert(:interval=>'1 day')
45
- #
46
- # To work around this, you can just add the necessary casts
47
- # manually:
48
- #
49
- # DB[:table].insert(:interval=>'1 day'.cast(:interval))
50
- #
51
- # You can also work around any issues that come up by disabling automatic
52
- # parameterization by calling the no_auto_parameterize method on the
53
- # dataset (which returns a clone of the dataset).
54
- #
55
- # It is likely there are other corner cases I am not yet aware of
56
- # when using this extension, so use this extension with caution.
57
- #
58
- # This extension is only compatible when using the pg driver, not
59
- # when using the old postgres driver or the postgres-pr driver.
60
-
61
- module Sequel
62
- module Postgres
63
- # Enable automatically parameterizing queries by hijacking the
64
- # SQL query string that Sequel builds to also hold the array
65
- # of parameters.
66
- module AutoParameterize
67
- # String that holds an array of parameters
68
- class StringWithArray < ::String
69
- PLACEHOLDER = '$'.freeze
70
- CAST = '::'.freeze
71
-
72
- # The array of parameters used by this query.
73
- attr_reader :args
74
-
75
- # Add a new parameter to this query, which adds
76
- # the parameter to the array of parameters, and an
77
- # SQL placeholder to the query itself.
78
- def add_arg(s, type=nil)
79
- @args ||= []
80
- @args << s
81
- self << PLACEHOLDER << @args.length.to_s
82
- self << CAST << type.to_s if type
83
- end
84
-
85
- # Show args when the string is inspected
86
- def inspect
87
- @args ? "#{self}; #{@args.inspect}".inspect : super
88
- end
89
- end
90
-
91
- module DatabaseMethods
92
- # Extend the database's datasets with the necessary code.
93
- def self.extended(db)
94
- Sequel::Deprecation.deprecate('The pg_auto_parameterize extension', 'Please stop loading it') unless defined?(SEQUEL_EXTENSIONS_NO_DEPRECATION_WARNING)
95
- db.extend_datasets(DatasetMethods)
96
- end
97
-
98
- # If the sql string has an embedded parameter array,
99
- # extract the arguments from that.
100
- def execute(sql, opts={})
101
- if sql.is_a?(StringWithArray) && (args = sql.args)
102
- opts = opts.merge(:arguments=>args)
103
- end
104
- super
105
- end
106
-
107
- private
108
-
109
- def create_view_sql(name, source, options)
110
- if source.is_a?(DatasetMethods)
111
- source = source.no_auto_parameterize
112
- end
113
- super
114
- end
115
- end
116
-
117
- module DatasetMethods
118
- # Return a clone of the dataset that will not do
119
- # automatic parameterization.
120
- def no_auto_parameterize
121
- clone(:no_auto_parameterize=>true)
122
- end
123
-
124
- # For strings, numeric arguments, and date/time arguments, add
125
- # them as parameters to the query instead of literalizing them
126
- # into the SQL.
127
- def literal_append(sql, v)
128
- if sql.is_a?(StringWithArray)
129
- case v
130
- when String
131
- case v
132
- when LiteralString
133
- super
134
- when Sequel::SQL::Blob
135
- sql.add_arg(v, :bytea)
136
- else
137
- sql.add_arg(v)
138
- end
139
- when Bignum
140
- sql.add_arg(v, :int8)
141
- when Fixnum
142
- sql.add_arg(v, :int4)
143
- when Float
144
- sql.add_arg(v, :"double precision")
145
- when BigDecimal
146
- sql.add_arg(v, :numeric)
147
- when Sequel::SQLTime
148
- sql.add_arg(v, :time)
149
- when Time, DateTime
150
- sql.add_arg(v, :timestamp)
151
- when Date
152
- sql.add_arg(v, :date)
153
- else
154
- super
155
- end
156
- else
157
- super
158
- end
159
- end
160
-
161
- def use_cursor(*)
162
- super.no_auto_parameterize
163
- end
164
-
165
- protected
166
-
167
- # Disable automatic parameterization for prepared statements,
168
- # since they will use manual parameterization.
169
- def to_prepared_statement(*a)
170
- opts[:no_auto_parameterize] ? super : no_auto_parameterize.to_prepared_statement(*a)
171
- end
172
-
173
- private
174
-
175
- # Unless auto parameterization is turned off, use a string that
176
- # can store the parameterized arguments.
177
- def sql_string_origin
178
- opts[:no_auto_parameterize] ? super : StringWithArray.new
179
- end
180
- end
181
- end
182
- end
183
-
184
- Database.register_extension(:pg_auto_parameterize, Postgres::AutoParameterize::DatabaseMethods)
185
- end
@@ -1,318 +0,0 @@
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.extension :pg_auto_parameterize, :pg_statement_cache
7
- # DB[:table].filter(:a=>1)
8
- # DB[:table].filter(:a=>2)
9
- # DB[:table].filter(:a=>3)
10
- #
11
- # And use the same prepared statement to execute the queries.
12
- #
13
- # The backbone of this extension is a modified LRU cache. It considers
14
- # both the last executed time and the number of executions when
15
- # determining which queries to keep in the cache. It only cleans the
16
- # cache when a high water mark has been passed, and removes queries
17
- # until it reaches the low water mark, in order to avoid thrashing when
18
- # you are using more than the maximum number of queries. To avoid
19
- # preparing queries when it isn't necessary, it does not prepare them
20
- # on the server side unless they are being executed more than once.
21
- # The cache is very tunable, allowing you to set the high and low
22
- # water marks, the number of executions before preparing the query,
23
- # and even use a custom callback for determine which queries to keep
24
- # in the cache.
25
- #
26
- # Note that automatically preparing statements does have some issues.
27
- # Most notably, if you change the result type that the query returns,
28
- # PostgreSQL will raise an error. This can happen if you have
29
- # prepared a statement that selects all columns from a table, and then
30
- # you add or remove a column from that table. This extension does
31
- # attempt to check that case and clear the statement caches if you use
32
- # alter_table from within Sequel, but it cannot fix the case when such
33
- # a change is made externally.
34
- #
35
- # This extension only works when the pg driver is used as the backend
36
- # for the postgres adapter.
37
-
38
- module Sequel
39
- module Postgres
40
- module StatementCache
41
- # A simple structure used for the values in the StatementCache's hash.
42
- # It does not hold the related SQL, since that is used as the key for
43
- # the StatementCache's hash.
44
- class Statement
45
- # The last time this statement was seen by the cache, persumably the
46
- # last time it was executed.
47
- attr_accessor :last_seen
48
-
49
- # The total number of executions since the statement entered the cache.
50
- attr_accessor :num_executes
51
-
52
- # The id related to the statement, used as part of the prepared statement
53
- # name.
54
- attr_reader :cache_id
55
-
56
- # Used when adding entries to the cache, just sets their id. Uses
57
- # 0 for num_executes since that is incremented elsewhere. Does not
58
- # set last_seen since that is set elsewhere to reduce branching.
59
- def initialize(cache_id)
60
- @num_executes = 0
61
- @cache_id = cache_id
62
- end
63
-
64
- # The name to use for the server side prepared statement. Note that this
65
- # statement might not actually be prepared.
66
- def name
67
- "sequel_pgap_#{cache_id}"
68
- end
69
- end
70
-
71
- # The backbone of the block, a modified LRU (least recently used) cache
72
- # mapping SQL query strings to Statement objects.
73
- class StatementCache
74
- include Enumerable
75
-
76
- # Set the options for the statement cache. These are generally set at
77
- # the database level using the :statement_cache_opts Database option.
78
- #
79
- # :max_size :: The maximum size (high water mark) for the cache. If
80
- # an entry is added when the current size of the cache is
81
- # equal to the maximum size, the cache is cleaned up to
82
- # reduce the number of entries to the :min_size. Defaults
83
- # to 1000.
84
- # :min_size :: The minimum size (low water mark) for the cache. On
85
- # cleanup, the size of the cache is reduced to this
86
- # number. Note that there could be fewer than this
87
- # number of entries in the cache. Defaults to :max_size/2.
88
- # :prepare_after :: The number of executions to wait for before preparing
89
- # the query server-side. If set to 1, prepares all executed
90
- # queries server-side. If set to 5, does not attempt to
91
- # prepare the query until the 5th execution. Defaults to 2.
92
- # :sorter :: A callable object that takes two arguments, the current time
93
- # and the related Statement instance, and should return some
94
- # Comparable (usually a numeric) such that the lowest values
95
- # returned are the first to be removed when it comes time to
96
- # clean the pool. The default is basically:
97
- #
98
- # lambda{|t, stmt| (stmt.last_seen - t)/stmt.num_executes}
99
- #
100
- # so that it doesn't remove statements that have been executed
101
- # many times just because many less-frequently executed statements
102
- # have been executed recently.
103
- #
104
- # The block passed is called with the Statement object's name, only for
105
- # statements that have been prepared, and should be used to deallocate the
106
- # statements.
107
- def initialize(opts={}, &block)
108
- @cleanup_proc = block
109
- @prepare_after = opts.fetch(:prepare_after, 2)
110
- @max_size = opts.fetch(:max_size, 1000)
111
- @min_size = opts.fetch(:min_size, @max_size/2)
112
- @sorter = opts.fetch(:sorter){method(:default_sorter)}
113
- @ids = (1..@max_size).to_a.reverse
114
- @hash = {}
115
- #
116
- # We add one so that when we clean the cache, the entry
117
- # about to be added brings us to the min_size.
118
- @size_diff = @max_size - @min_size + 1
119
- end
120
-
121
- # Completely clear the statement cache, deallocating on
122
- # the server side all statements that have been prepared.
123
- def clear
124
- @hash.keys.each{|k| remove(k)}
125
- end
126
-
127
- # Yield each SQL string and Statement instance in the cache
128
- # to the block.
129
- def each(&block)
130
- @hash.each(&block)
131
- end
132
-
133
- # Get the related statement name from the cache. If the
134
- # entry is already in the cache, just bump it's last seen
135
- # time and the number of executions. Otherwise, add it
136
- # to the cache. If the cache is already full, clean it up
137
- # before adding it.
138
- #
139
- # If the num of executions has passed the threshhold, yield
140
- # the statement name to the block, which should be used to
141
- # prepare the statement on the server side.
142
- #
143
- # This method should return the prepared statment name if
144
- # the statement has been prepared, and nil if the query
145
- # has not been prepared and the statement should be executed
146
- # normally.
147
- def fetch(sql)
148
- unless stmt = @hash[sql]
149
- # Get the next id from the id pool.
150
- unless id = @ids.pop
151
- # No id left, cache must be full, so cleanup and then
152
- # get the next id from the id pool.
153
- cleanup
154
- id = @ids.pop
155
- end
156
- @hash[sql] = stmt = Statement.new(id)
157
- end
158
-
159
- stmt.last_seen = Time.now
160
- stmt.num_executes += 1
161
-
162
- if stmt.num_executes >= @prepare_after
163
- if stmt.num_executes == @prepare_after
164
- begin
165
- yield(stmt.name)
166
- rescue PGError
167
- # An error occurred while preparing the statement,
168
- # execute it normally (which will probably raise
169
- # the error again elsewhere), but decrement the
170
- # number of executions so we don't think we've
171
- # prepared the statement when we haven't.
172
- stmt.num_executes -= 1
173
- return nil
174
- end
175
- end
176
- stmt.name
177
- end
178
- end
179
-
180
- # The current size of the statement cache.
181
- def size
182
- @hash.length
183
- end
184
-
185
- private
186
-
187
- # Sort by time since last execution and number of executions.
188
- # We don't want to throw stuff out of the
189
- # cache if it has been executed a lot,
190
- # but a bunch of queries that were
191
- # executed only once came in more recently.
192
- def default_sorter(t, stmt)
193
- (stmt.last_seen - t)/stmt.num_executes
194
- end
195
-
196
- # After sorting the cache appropriately (so that the least important
197
- # items are first), reduce the number of entries in the cache to
198
- # the low water mark by removing the related query. Should only be
199
- # called when the cache is full.
200
- def cleanup
201
- t = Time.now
202
- @hash.sort_by{|k,v| @sorter.call(t, v)}.first(@size_diff).each{|sql, stmt| remove(sql)}
203
- end
204
-
205
- # Remove the query from the cache. If it has been prepared,
206
- # call the cleanup_proc to deallocate the statement.
207
- def remove(sql)
208
- stmt = @hash.delete(sql)
209
- if stmt.num_executes >= @prepare_after
210
- @cleanup_proc.call(stmt.name)
211
- end
212
-
213
- # Return id to the pool of ids
214
- @ids.push(stmt.cache_id)
215
- end
216
- end
217
-
218
- module AdapterMethods
219
- # A regular expression for the types of queries to cache. Any queries not
220
- # matching this regular expression are not cached.
221
- DML_RE = /\A(WITH|SELECT|INSERT|UPDATE|DELETE) /
222
-
223
- # The StatementCache instance for this connection. Note that
224
- # each connection has a separate StatementCache, because prepared
225
- # statements are connection-specific.
226
- attr_reader :statement_cache
227
-
228
- # Set the statement_cache for the connection, using the database's
229
- # :statement_cache_opts option.
230
- def self.extended(c)
231
- Sequel::Deprecation.deprecate('The pg_statement_cache extension', 'Please stop loading it') unless defined?(SEQUEL_EXTENSIONS_NO_DEPRECATION_WARNING)
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
-
317
- Database.register_extension(:pg_statement_cache, Postgres::StatementCache::DatabaseMethods)
318
- end