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.
- 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
|