sequel 3.48.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +114 -0
- data/Rakefile +10 -7
- data/doc/association_basics.rdoc +25 -23
- data/doc/code_order.rdoc +7 -0
- data/doc/core_extensions.rdoc +0 -10
- data/doc/object_model.rdoc +4 -1
- data/doc/querying.rdoc +3 -3
- data/doc/release_notes/4.0.0.txt +262 -0
- data/doc/security.rdoc +0 -28
- data/doc/testing.rdoc +8 -14
- data/lib/sequel/adapters/ado.rb +7 -11
- data/lib/sequel/adapters/ado/access.rb +8 -8
- data/lib/sequel/adapters/ado/mssql.rb +4 -4
- data/lib/sequel/adapters/amalgalite.rb +6 -6
- data/lib/sequel/adapters/cubrid.rb +7 -7
- data/lib/sequel/adapters/db2.rb +5 -9
- data/lib/sequel/adapters/dbi.rb +2 -6
- data/lib/sequel/adapters/do.rb +4 -4
- data/lib/sequel/adapters/firebird.rb +4 -4
- data/lib/sequel/adapters/ibmdb.rb +8 -8
- data/lib/sequel/adapters/informix.rb +2 -10
- data/lib/sequel/adapters/jdbc.rb +17 -17
- data/lib/sequel/adapters/jdbc/as400.rb +2 -2
- data/lib/sequel/adapters/jdbc/cubrid.rb +1 -1
- data/lib/sequel/adapters/jdbc/db2.rb +1 -1
- data/lib/sequel/adapters/jdbc/derby.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +2 -2
- data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
- data/lib/sequel/adapters/jdbc/informix.rb +1 -1
- data/lib/sequel/adapters/jdbc/mssql.rb +2 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
- data/lib/sequel/adapters/jdbc/oracle.rb +5 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -3
- data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
- data/lib/sequel/adapters/jdbc/transactions.rb +3 -3
- data/lib/sequel/adapters/mock.rb +7 -7
- data/lib/sequel/adapters/mysql.rb +3 -3
- data/lib/sequel/adapters/mysql2.rb +4 -4
- data/lib/sequel/adapters/odbc.rb +2 -6
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +1 -5
- data/lib/sequel/adapters/oracle.rb +13 -17
- data/lib/sequel/adapters/postgres.rb +20 -25
- data/lib/sequel/adapters/shared/cubrid.rb +3 -3
- data/lib/sequel/adapters/shared/db2.rb +2 -2
- data/lib/sequel/adapters/shared/firebird.rb +7 -7
- data/lib/sequel/adapters/shared/mssql.rb +9 -9
- data/lib/sequel/adapters/shared/mysql.rb +29 -13
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +7 -7
- data/lib/sequel/adapters/shared/oracle.rb +22 -13
- data/lib/sequel/adapters/shared/postgres.rb +61 -46
- data/lib/sequel/adapters/shared/sqlite.rb +9 -9
- data/lib/sequel/adapters/sqlite.rb +17 -11
- data/lib/sequel/adapters/swift.rb +3 -3
- data/lib/sequel/adapters/swift/mysql.rb +1 -1
- data/lib/sequel/adapters/swift/sqlite.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +8 -8
- data/lib/sequel/ast_transformer.rb +3 -1
- data/lib/sequel/connection_pool.rb +4 -2
- data/lib/sequel/connection_pool/sharded_single.rb +2 -2
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -5
- data/lib/sequel/connection_pool/threaded.rb +7 -7
- data/lib/sequel/core.rb +4 -67
- data/lib/sequel/database.rb +1 -0
- data/lib/sequel/database/connecting.rb +2 -8
- data/lib/sequel/database/dataset.rb +2 -7
- data/lib/sequel/database/dataset_defaults.rb +0 -18
- data/lib/sequel/database/features.rb +4 -4
- data/lib/sequel/database/misc.rb +6 -8
- data/lib/sequel/database/query.rb +5 -61
- data/lib/sequel/database/schema_generator.rb +22 -20
- data/lib/sequel/database/schema_methods.rb +48 -20
- data/lib/sequel/database/transactions.rb +7 -17
- data/lib/sequel/dataset.rb +2 -0
- data/lib/sequel/dataset/actions.rb +23 -91
- data/lib/sequel/dataset/features.rb +1 -4
- data/lib/sequel/dataset/graph.rb +3 -47
- data/lib/sequel/dataset/misc.rb +4 -33
- data/lib/sequel/dataset/prepared_statements.rb +3 -1
- data/lib/sequel/dataset/query.rb +116 -240
- data/lib/sequel/dataset/sql.rb +19 -97
- data/lib/sequel/deprecated.rb +0 -16
- data/lib/sequel/exceptions.rb +0 -3
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/columns_introspection.rb +1 -12
- data/lib/sequel/extensions/constraint_validations.rb +3 -3
- data/lib/sequel/extensions/core_extensions.rb +0 -9
- data/lib/sequel/extensions/date_arithmetic.rb +1 -2
- data/lib/sequel/extensions/graph_each.rb +11 -0
- data/lib/sequel/extensions/migration.rb +5 -5
- data/lib/sequel/extensions/null_dataset.rb +11 -13
- data/lib/sequel/extensions/pagination.rb +3 -6
- data/lib/sequel/extensions/pg_array.rb +6 -4
- data/lib/sequel/extensions/pg_array_ops.rb +35 -1
- data/lib/sequel/extensions/pg_json.rb +12 -2
- data/lib/sequel/extensions/pg_json_ops.rb +266 -0
- data/lib/sequel/extensions/pg_range.rb +2 -2
- data/lib/sequel/extensions/pg_range_ops.rb +0 -8
- data/lib/sequel/extensions/pg_row.rb +2 -2
- data/lib/sequel/extensions/pretty_table.rb +0 -4
- data/lib/sequel/extensions/query.rb +3 -8
- data/lib/sequel/extensions/schema_caching.rb +0 -7
- data/lib/sequel/extensions/schema_dumper.rb +10 -17
- data/lib/sequel/extensions/select_remove.rb +0 -4
- data/lib/sequel/extensions/set_overrides.rb +28 -0
- data/lib/sequel/extensions/to_dot.rb +6 -10
- data/lib/sequel/model.rb +6 -7
- data/lib/sequel/model/associations.rb +127 -182
- data/lib/sequel/model/base.rb +88 -211
- data/lib/sequel/model/errors.rb +0 -13
- data/lib/sequel/model/plugins.rb +2 -2
- data/lib/sequel/no_core_ext.rb +0 -1
- data/lib/sequel/plugins/after_initialize.rb +11 -17
- data/lib/sequel/plugins/association_autoreloading.rb +1 -47
- data/lib/sequel/plugins/association_dependencies.rb +2 -2
- data/lib/sequel/plugins/auto_validations.rb +2 -8
- data/lib/sequel/plugins/blacklist_security.rb +32 -2
- data/lib/sequel/plugins/caching.rb +1 -1
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/composition.rb +10 -8
- data/lib/sequel/plugins/constraint_validations.rb +2 -2
- data/lib/sequel/plugins/dataset_associations.rb +4 -0
- data/lib/sequel/plugins/defaults_setter.rb +8 -6
- data/lib/sequel/plugins/dirty.rb +6 -6
- data/lib/sequel/plugins/force_encoding.rb +13 -8
- data/lib/sequel/plugins/hook_class_methods.rb +1 -7
- data/lib/sequel/plugins/json_serializer.rb +13 -74
- data/lib/sequel/plugins/lazy_attributes.rb +2 -4
- data/lib/sequel/plugins/list.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +4 -11
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +1 -49
- data/lib/sequel/plugins/nested_attributes.rb +1 -1
- data/lib/sequel/plugins/optimistic_locking.rb +3 -5
- data/lib/sequel/plugins/pg_array_associations.rb +453 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +23 -7
- data/lib/sequel/plugins/prepared_statements.rb +1 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +20 -14
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -2
- data/lib/sequel/plugins/rcte_tree.rb +1 -1
- data/lib/sequel/plugins/serialization.rb +5 -4
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
- data/lib/sequel/plugins/sharding.rb +7 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/touch.rb +2 -2
- data/lib/sequel/plugins/tree.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +19 -4
- data/lib/sequel/plugins/validation_class_methods.rb +0 -30
- data/lib/sequel/plugins/validation_helpers.rb +13 -31
- data/lib/sequel/plugins/xml_serializer.rb +18 -57
- data/lib/sequel/sql.rb +20 -22
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/db2_spec.rb +14 -23
- data/spec/adapters/firebird_spec.rb +25 -29
- data/spec/adapters/informix_spec.rb +11 -14
- data/spec/adapters/mssql_spec.rb +71 -77
- data/spec/adapters/mysql_spec.rb +165 -172
- data/spec/adapters/oracle_spec.rb +36 -39
- data/spec/adapters/postgres_spec.rb +175 -100
- data/spec/adapters/spec_helper.rb +13 -11
- data/spec/adapters/sqlite_spec.rb +36 -44
- data/spec/core/connection_pool_spec.rb +2 -1
- data/spec/core/database_spec.rb +55 -55
- data/spec/core/dataset_spec.rb +45 -249
- data/spec/core/deprecated_spec.rb +0 -8
- data/spec/core/expression_filters_spec.rb +23 -5
- data/spec/core/object_graph_spec.rb +4 -66
- data/spec/core/schema_spec.rb +35 -12
- data/spec/core/spec_helper.rb +3 -2
- data/spec/core_extensions_spec.rb +17 -19
- data/spec/extensions/arbitrary_servers_spec.rb +2 -3
- data/spec/extensions/association_dependencies_spec.rb +14 -14
- data/spec/extensions/auto_validations_spec.rb +7 -0
- data/spec/extensions/blacklist_security_spec.rb +5 -5
- data/spec/extensions/blank_spec.rb +2 -0
- data/spec/extensions/class_table_inheritance_spec.rb +2 -2
- data/spec/extensions/columns_introspection_spec.rb +2 -29
- data/spec/extensions/composition_spec.rb +10 -17
- data/spec/extensions/core_refinements_spec.rb +5 -1
- data/spec/extensions/dataset_associations_spec.rb +18 -0
- data/spec/extensions/date_arithmetic_spec.rb +2 -2
- data/spec/extensions/defaults_setter_spec.rb +9 -9
- data/spec/extensions/dirty_spec.rb +0 -5
- data/spec/extensions/eval_inspect_spec.rb +2 -0
- data/spec/extensions/force_encoding_spec.rb +2 -18
- data/spec/extensions/hash_aliases_spec.rb +8 -0
- data/spec/extensions/hook_class_methods_spec.rb +39 -58
- data/spec/extensions/inflector_spec.rb +2 -0
- data/spec/extensions/instance_filters_spec.rb +8 -8
- data/spec/extensions/json_serializer_spec.rb +1 -41
- data/spec/extensions/list_spec.rb +1 -1
- data/spec/extensions/many_through_many_spec.rb +106 -109
- data/spec/extensions/migration_spec.rb +2 -0
- data/spec/extensions/named_timezones_spec.rb +1 -0
- data/spec/extensions/pg_array_associations_spec.rb +603 -0
- data/spec/extensions/pg_array_ops_spec.rb +25 -0
- data/spec/extensions/pg_array_spec.rb +9 -1
- data/spec/extensions/pg_hstore_ops_spec.rb +13 -0
- data/spec/extensions/pg_hstore_spec.rb +1 -0
- data/spec/extensions/pg_json_ops_spec.rb +131 -0
- data/spec/extensions/pg_json_spec.rb +10 -4
- data/spec/extensions/pg_range_ops_spec.rb +2 -5
- data/spec/extensions/pg_range_spec.rb +6 -2
- data/spec/extensions/pg_row_ops_spec.rb +2 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +26 -5
- data/spec/extensions/rcte_tree_spec.rb +15 -15
- data/spec/extensions/schema_dumper_spec.rb +0 -1
- data/spec/extensions/schema_spec.rb +9 -9
- data/spec/extensions/serialization_modification_detection_spec.rb +1 -1
- data/spec/extensions/serialization_spec.rb +18 -29
- data/spec/extensions/set_overrides_spec.rb +4 -0
- data/spec/extensions/{many_to_one_pk_lookup_spec.rb → shared_caching_spec.rb} +1 -4
- data/spec/extensions/single_table_inheritance_spec.rb +4 -4
- data/spec/extensions/spec_helper.rb +8 -9
- data/spec/extensions/sql_expr_spec.rb +2 -0
- data/spec/extensions/string_date_time_spec.rb +2 -0
- data/spec/extensions/string_stripper_spec.rb +2 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +12 -12
- data/spec/extensions/thread_local_timezones_spec.rb +2 -0
- data/spec/extensions/timestamps_spec.rb +1 -1
- data/spec/extensions/to_dot_spec.rb +1 -1
- data/spec/extensions/touch_spec.rb +24 -24
- data/spec/extensions/tree_spec.rb +7 -7
- data/spec/extensions/typecast_on_load_spec.rb +8 -1
- data/spec/extensions/update_primary_key_spec.rb +10 -10
- data/spec/extensions/validation_class_methods_spec.rb +10 -39
- data/spec/extensions/validation_helpers_spec.rb +29 -47
- data/spec/extensions/xml_serializer_spec.rb +1 -23
- data/spec/integration/associations_test.rb +231 -40
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +64 -64
- data/spec/integration/eager_loader_test.rb +28 -28
- data/spec/integration/migrator_test.rb +1 -1
- data/spec/integration/model_test.rb +2 -2
- data/spec/integration/plugin_test.rb +21 -21
- data/spec/integration/prepared_statement_test.rb +7 -7
- data/spec/integration/schema_test.rb +115 -110
- data/spec/integration/spec_helper.rb +17 -27
- data/spec/integration/timezone_test.rb +1 -1
- data/spec/integration/transaction_test.rb +10 -10
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +2 -28
- data/spec/model/associations_spec.rb +239 -188
- data/spec/model/base_spec.rb +27 -68
- data/spec/model/dataset_methods_spec.rb +4 -4
- data/spec/model/eager_loading_spec.rb +160 -172
- data/spec/model/hooks_spec.rb +62 -79
- data/spec/model/model_spec.rb +36 -51
- data/spec/model/plugins_spec.rb +5 -19
- data/spec/model/record_spec.rb +125 -151
- data/spec/model/spec_helper.rb +8 -6
- data/spec/model/validations_spec.rb +4 -17
- data/spec/spec_config.rb +2 -10
- metadata +50 -56
- data/lib/sequel/deprecated_core_extensions.rb +0 -135
- data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -185
- data/lib/sequel/extensions/pg_statement_cache.rb +0 -318
- data/lib/sequel/plugins/identity_map.rb +0 -260
- data/lib/sequel_core.rb +0 -2
- data/lib/sequel_model.rb +0 -2
- data/spec/extensions/association_autoreloading_spec.rb +0 -102
- data/spec/extensions/identity_map_spec.rb +0 -337
- data/spec/extensions/pg_auto_parameterize_spec.rb +0 -70
- data/spec/extensions/pg_statement_cache_spec.rb +0 -208
- data/spec/rcov.opts +0 -8
- 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
|