sequel 3.48.0 → 4.0.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 (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