sequel 3.21.0 → 3.36.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.
- data/CHANGELOG +855 -3
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -7
- data/Rakefile +42 -24
- data/bin/sequel +26 -8
- data/doc/active_record.rdoc +1 -0
- data/doc/advanced_associations.rdoc +171 -11
- data/doc/association_basics.rdoc +293 -43
- data/doc/dataset_basics.rdoc +5 -5
- data/doc/dataset_filtering.rdoc +8 -0
- data/doc/mass_assignment.rdoc +56 -0
- data/doc/migration.rdoc +85 -552
- data/doc/model_hooks.rdoc +78 -27
- data/doc/object_model.rdoc +541 -0
- data/doc/opening_databases.rdoc +97 -50
- data/doc/prepared_statements.rdoc +40 -14
- data/doc/querying.rdoc +28 -4
- data/doc/reflection.rdoc +30 -4
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/release_notes/3.30.0.txt +135 -0
- data/doc/release_notes/3.31.0.txt +146 -0
- data/doc/release_notes/3.32.0.txt +202 -0
- data/doc/release_notes/3.33.0.txt +157 -0
- data/doc/release_notes/3.34.0.txt +671 -0
- data/doc/release_notes/3.35.0.txt +144 -0
- data/doc/release_notes/3.36.0.txt +245 -0
- data/doc/schema_modification.rdoc +572 -0
- data/doc/sharding.rdoc +95 -3
- data/doc/testing.rdoc +129 -0
- data/doc/thread_safety.rdoc +17 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/virtual_rows.rdoc +51 -2
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/ado.rb +40 -18
- data/lib/sequel/adapters/amalgalite.rb +17 -9
- data/lib/sequel/adapters/db2.rb +175 -86
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do/mysql.rb +17 -11
- data/lib/sequel/adapters/do/postgres.rb +1 -57
- data/lib/sequel/adapters/do/sqlite.rb +0 -10
- data/lib/sequel/adapters/do.rb +18 -36
- data/lib/sequel/adapters/firebird.rb +27 -208
- data/lib/sequel/adapters/ibmdb.rb +453 -0
- data/lib/sequel/adapters/informix.rb +6 -23
- data/lib/sequel/adapters/jdbc/as400.rb +16 -34
- data/lib/sequel/adapters/jdbc/db2.rb +47 -0
- data/lib/sequel/adapters/jdbc/derby.rb +287 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
- data/lib/sequel/adapters/jdbc/h2.rb +73 -31
- data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
- data/lib/sequel/adapters/jdbc/informix.rb +21 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
- data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
- data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
- data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
- data/lib/sequel/adapters/jdbc/progress.rb +21 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
- data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +265 -94
- data/lib/sequel/adapters/mock.rb +361 -0
- data/lib/sequel/adapters/mysql.rb +118 -222
- data/lib/sequel/adapters/mysql2.rb +58 -57
- data/lib/sequel/adapters/odbc/mssql.rb +15 -7
- data/lib/sequel/adapters/odbc.rb +28 -9
- data/lib/sequel/adapters/openbase.rb +3 -5
- data/lib/sequel/adapters/oracle.rb +349 -53
- data/lib/sequel/adapters/postgres.rb +356 -107
- data/lib/sequel/adapters/shared/access.rb +32 -9
- data/lib/sequel/adapters/shared/db2.rb +342 -0
- data/lib/sequel/adapters/shared/firebird.rb +221 -0
- data/lib/sequel/adapters/shared/informix.rb +53 -0
- data/lib/sequel/adapters/shared/mssql.rb +335 -118
- data/lib/sequel/adapters/shared/mysql.rb +340 -76
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
- data/lib/sequel/adapters/shared/oracle.rb +243 -45
- data/lib/sequel/adapters/shared/postgres.rb +524 -279
- data/lib/sequel/adapters/shared/progress.rb +5 -8
- data/lib/sequel/adapters/shared/sqlite.rb +265 -89
- data/lib/sequel/adapters/sqlite.rb +101 -39
- data/lib/sequel/adapters/swift/mysql.rb +9 -12
- data/lib/sequel/adapters/swift/postgres.rb +9 -74
- data/lib/sequel/adapters/swift/sqlite.rb +8 -6
- data/lib/sequel/adapters/swift.rb +8 -7
- data/lib/sequel/adapters/tinytds.rb +161 -21
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
- data/lib/sequel/ast_transformer.rb +194 -0
- data/lib/sequel/connection_pool/sharded_single.rb +11 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
- data/lib/sequel/connection_pool/single.rb +5 -0
- data/lib/sequel/connection_pool/threaded.rb +38 -14
- data/lib/sequel/connection_pool.rb +10 -2
- data/lib/sequel/core.rb +118 -11
- data/lib/sequel/database/connecting.rb +38 -28
- data/lib/sequel/database/dataset.rb +4 -4
- data/lib/sequel/database/dataset_defaults.rb +63 -2
- data/lib/sequel/database/logging.rb +7 -2
- data/lib/sequel/database/misc.rb +150 -46
- data/lib/sequel/database/query.rb +215 -65
- data/lib/sequel/database/schema_generator.rb +64 -20
- data/lib/sequel/database/schema_methods.rb +212 -54
- data/lib/sequel/dataset/actions.rb +322 -85
- data/lib/sequel/dataset/features.rb +96 -8
- data/lib/sequel/dataset/graph.rb +38 -11
- data/lib/sequel/dataset/misc.rb +50 -23
- data/lib/sequel/dataset/mutation.rb +12 -7
- data/lib/sequel/dataset/prepared_statements.rb +68 -30
- data/lib/sequel/dataset/query.rb +237 -44
- data/lib/sequel/dataset/sql.rb +698 -278
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/_pretty_table.rb +79 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
- data/lib/sequel/extensions/migration.rb +97 -11
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/null_dataset.rb +90 -0
- data/lib/sequel/extensions/pg_array.rb +460 -0
- data/lib/sequel/extensions/pg_array_ops.rb +220 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
- data/lib/sequel/extensions/pg_hstore.rb +296 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
- data/lib/sequel/extensions/pg_inet.rb +89 -0
- data/lib/sequel/extensions/pg_json.rb +178 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
- data/lib/sequel/extensions/pretty_table.rb +5 -71
- data/lib/sequel/extensions/query.rb +1 -1
- data/lib/sequel/extensions/query_literals.rb +79 -0
- data/lib/sequel/extensions/schema_caching.rb +76 -0
- data/lib/sequel/extensions/schema_dumper.rb +251 -37
- data/lib/sequel/extensions/select_remove.rb +35 -0
- data/lib/sequel/extensions/server_block.rb +139 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -110
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +1187 -393
- data/lib/sequel/model/base.rb +578 -170
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model.rb +30 -11
- data/lib/sequel/no_core_ext.rb +2 -0
- data/lib/sequel/plugins/active_model.rb +13 -1
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/caching.rb +25 -18
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/composition.rb +44 -12
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/dirty.rb +214 -0
- data/lib/sequel/plugins/eager_each.rb +59 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/identity_map.rb +123 -8
- data/lib/sequel/plugins/instance_filters.rb +10 -0
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +58 -6
- data/lib/sequel/plugins/list.rb +12 -2
- data/lib/sequel/plugins/many_through_many.rb +79 -28
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +139 -63
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/prepared_statements.rb +167 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/schema.rb +7 -2
- data/lib/sequel/plugins/serialization.rb +98 -49
- data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
- data/lib/sequel/plugins/sharding.rb +21 -54
- data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
- data/lib/sequel/plugins/static_cache.rb +99 -0
- data/lib/sequel/plugins/subclasses.rb +10 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +15 -4
- data/lib/sequel/sql.rb +526 -59
- data/lib/sequel/timezones.rb +57 -38
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/firebird_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +142 -40
- data/spec/adapters/mysql_spec.rb +380 -234
- data/spec/adapters/oracle_spec.rb +67 -77
- data/spec/adapters/postgres_spec.rb +1050 -212
- data/spec/adapters/spec_helper.rb +8 -5
- data/spec/adapters/sqlite_spec.rb +139 -27
- data/spec/core/connection_pool_spec.rb +196 -93
- data/spec/core/database_spec.rb +879 -510
- data/spec/core/dataset_spec.rb +1415 -1086
- data/spec/core/expression_filters_spec.rb +554 -109
- data/spec/core/mock_adapter_spec.rb +449 -0
- data/spec/core/object_graph_spec.rb +86 -114
- data/spec/core/schema_generator_spec.rb +4 -4
- data/spec/core/schema_spec.rb +482 -38
- data/spec/core/spec_helper.rb +6 -48
- data/spec/extensions/active_model_spec.rb +13 -0
- data/spec/extensions/arbitrary_servers_spec.rb +110 -0
- data/spec/extensions/association_autoreloading_spec.rb +18 -10
- data/spec/extensions/association_dependencies_spec.rb +15 -25
- data/spec/extensions/association_pks_spec.rb +66 -32
- data/spec/extensions/association_proxies_spec.rb +4 -4
- data/spec/extensions/caching_spec.rb +47 -51
- data/spec/extensions/class_table_inheritance_spec.rb +31 -83
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/composition_spec.rb +18 -13
- data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +69 -92
- data/spec/extensions/identity_map_spec.rb +131 -22
- data/spec/extensions/inflector_spec.rb +0 -4
- data/spec/extensions/instance_filters_spec.rb +11 -21
- data/spec/extensions/instance_hooks_spec.rb +72 -0
- data/spec/extensions/json_serializer_spec.rb +49 -6
- data/spec/extensions/lazy_attributes_spec.rb +16 -20
- data/spec/extensions/list_spec.rb +48 -39
- data/spec/extensions/many_through_many_spec.rb +283 -250
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
- data/spec/extensions/migration_spec.rb +140 -35
- data/spec/extensions/named_timezones_spec.rb +29 -11
- data/spec/extensions/nested_attributes_spec.rb +251 -89
- data/spec/extensions/null_dataset_spec.rb +85 -0
- data/spec/extensions/optimistic_locking_spec.rb +24 -21
- data/spec/extensions/pg_array_ops_spec.rb +105 -0
- data/spec/extensions/pg_array_spec.rb +196 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
- data/spec/extensions/pg_hstore_spec.rb +195 -0
- data/spec/extensions/pg_inet_spec.rb +44 -0
- data/spec/extensions/pg_json_spec.rb +101 -0
- data/spec/extensions/pg_statement_cache_spec.rb +209 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
- data/spec/extensions/prepared_statements_spec.rb +87 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
- data/spec/extensions/pretty_table_spec.rb +7 -6
- data/spec/extensions/query_literals_spec.rb +168 -0
- data/spec/extensions/query_spec.rb +6 -0
- data/spec/extensions/rcte_tree_spec.rb +50 -43
- data/spec/extensions/schema_caching_spec.rb +41 -0
- data/spec/extensions/schema_dumper_spec.rb +429 -22
- data/spec/extensions/schema_spec.rb +25 -21
- data/spec/extensions/select_remove_spec.rb +38 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
- data/spec/extensions/serialization_spec.rb +85 -43
- data/spec/extensions/server_block_spec.rb +90 -0
- data/spec/extensions/sharding_spec.rb +69 -143
- data/spec/extensions/single_table_inheritance_spec.rb +23 -29
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +22 -61
- data/spec/extensions/sql_expr_spec.rb +29 -60
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/timestamps_spec.rb +6 -6
- data/spec/extensions/to_dot_spec.rb +4 -7
- data/spec/extensions/touch_spec.rb +13 -14
- data/spec/extensions/tree_spec.rb +11 -26
- data/spec/extensions/typecast_on_load_spec.rb +9 -6
- data/spec/extensions/update_primary_key_spec.rb +30 -24
- data/spec/extensions/validation_class_methods_spec.rb +30 -51
- data/spec/extensions/validation_helpers_spec.rb +16 -35
- data/spec/extensions/xml_serializer_spec.rb +29 -4
- data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
- data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
- data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
- data/spec/integration/associations_test.rb +659 -21
- data/spec/integration/database_test.rb +9 -3
- data/spec/integration/dataset_test.rb +605 -91
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +81 -2
- data/spec/integration/plugin_test.rb +454 -143
- data/spec/integration/prepared_statement_test.rb +263 -128
- data/spec/integration/schema_test.rb +309 -43
- data/spec/integration/spec_helper.rb +40 -30
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +183 -8
- data/spec/integration/type_test.rb +18 -4
- data/spec/model/association_reflection_spec.rb +201 -7
- data/spec/model/associations_spec.rb +1067 -661
- data/spec/model/base_spec.rb +329 -95
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +677 -676
- data/spec/model/hooks_spec.rb +349 -67
- data/spec/model/model_spec.rb +240 -168
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +490 -232
- data/spec/model/spec_helper.rb +14 -71
- data/spec/model/validations_spec.rb +11 -0
- data/spec/rcov.opts +2 -0
- metadata +178 -80
|
@@ -0,0 +1,169 @@
|
|
|
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.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
|
|
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('2, 1'.lit)
|
|
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
|
+
# The array of parameters used by this query.
|
|
70
|
+
attr_reader :args
|
|
71
|
+
|
|
72
|
+
# Add a new parameter to this query, which adds
|
|
73
|
+
# the parameter to the array of parameters, and an
|
|
74
|
+
# SQL placeholder to the query itself.
|
|
75
|
+
def add_arg(s, type)
|
|
76
|
+
@args ||= []
|
|
77
|
+
@args << s
|
|
78
|
+
self << "$#{@args.length}::#{type}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Show args when the string is inspected
|
|
82
|
+
def inspect
|
|
83
|
+
@args ? "#{self}; #{@args.inspect}".inspect : super
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module DatabaseMethods
|
|
88
|
+
# Extend the database's datasets with the necessary code.
|
|
89
|
+
def self.extended(db)
|
|
90
|
+
db.extend_datasets(DatasetMethods)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# If the sql string has an embedded parameter array,
|
|
94
|
+
# extract the arguments from that.
|
|
95
|
+
def execute(sql, opts={})
|
|
96
|
+
if sql.is_a?(StringWithArray) && (args = sql.args)
|
|
97
|
+
opts = opts.merge(:arguments=>args)
|
|
98
|
+
end
|
|
99
|
+
super
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
module DatasetMethods
|
|
104
|
+
# Return a clone of the dataset that will not do
|
|
105
|
+
# automatic parameterization.
|
|
106
|
+
def no_auto_parameterize
|
|
107
|
+
clone(:no_auto_parameterize=>true)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# For strings, numeric arguments, and date/time arguments, add
|
|
111
|
+
# them as parameters to the query instead of literalizing them
|
|
112
|
+
# into the SQL.
|
|
113
|
+
def literal_append(sql, v)
|
|
114
|
+
if sql.is_a?(StringWithArray)
|
|
115
|
+
case v
|
|
116
|
+
when String
|
|
117
|
+
case v
|
|
118
|
+
when LiteralString
|
|
119
|
+
super
|
|
120
|
+
when Sequel::SQL::Blob
|
|
121
|
+
sql.add_arg(v, :bytea)
|
|
122
|
+
else
|
|
123
|
+
sql.add_arg(v, :text)
|
|
124
|
+
end
|
|
125
|
+
when Bignum
|
|
126
|
+
sql.add_arg(v, :int8)
|
|
127
|
+
when Fixnum
|
|
128
|
+
sql.add_arg(v, :int4)
|
|
129
|
+
when Float
|
|
130
|
+
sql.add_arg(v, :"double precision")
|
|
131
|
+
when BigDecimal
|
|
132
|
+
sql.add_arg(v, :numeric)
|
|
133
|
+
when Sequel::SQLTime
|
|
134
|
+
sql.add_arg(v, :time)
|
|
135
|
+
when Time, DateTime
|
|
136
|
+
sql.add_arg(v, :timestamp)
|
|
137
|
+
when Date
|
|
138
|
+
sql.add_arg(v, :date)
|
|
139
|
+
else
|
|
140
|
+
super
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
super
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def use_cursor(*)
|
|
148
|
+
super.no_auto_parameterize
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
protected
|
|
152
|
+
|
|
153
|
+
# Disable automatic parameterization for prepared statements,
|
|
154
|
+
# since they will use manual parameterization.
|
|
155
|
+
def to_prepared_statement(*a)
|
|
156
|
+
opts[:no_auto_parameterize] ? super : no_auto_parameterize.to_prepared_statement(*a)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
# Unless auto parameterization is turned off, use a string that
|
|
162
|
+
# can store the parameterized arguments.
|
|
163
|
+
def sql_string_origin
|
|
164
|
+
opts[:no_auto_parameterize] ? super : StringWithArray.new
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# The pg_hstore extension adds support for the PostgreSQL hstore type
|
|
2
|
+
# to Sequel. hstore is an extension that ships with PostgreSQL, and
|
|
3
|
+
# the hstore type stores an arbitrary key-value table, where the keys
|
|
4
|
+
# are strings and the values are strings or NULL.
|
|
5
|
+
#
|
|
6
|
+
# This extension integrates with Sequel's native postgres adapter, so
|
|
7
|
+
# that when hstore fields are retrieved, they are parsed and returned
|
|
8
|
+
# as instances of Sequel::Postgres::HStore. HStore is
|
|
9
|
+
# a DelegateClass of Hash, so it mostly acts like a hash, but not
|
|
10
|
+
# completely (is_a?(Hash) is false). If you want the actual hash,
|
|
11
|
+
# you can call Hstore#to_hash. This is done so that Sequel does not
|
|
12
|
+
# treat a HStore like a Hash by default, which would cause issues.
|
|
13
|
+
#
|
|
14
|
+
# In addition to the parsers, this extension comes with literalizers
|
|
15
|
+
# for HStore using the standard Sequel literalization callbacks, so
|
|
16
|
+
# they work with on all adapters.
|
|
17
|
+
#
|
|
18
|
+
# To turn an existing Hash into an HStore:
|
|
19
|
+
#
|
|
20
|
+
# hash.hstore
|
|
21
|
+
#
|
|
22
|
+
# Since the hstore type only supports strings, non string keys and
|
|
23
|
+
# values are converted to strings
|
|
24
|
+
#
|
|
25
|
+
# {:foo=>1}.hstore.to_hash # {'foo'=>'1'}
|
|
26
|
+
# v = {}.hstore
|
|
27
|
+
# v[:foo] = 1
|
|
28
|
+
# v # {'foo'=>'1'}
|
|
29
|
+
#
|
|
30
|
+
# However, to make life easier, lookups by key are converted to
|
|
31
|
+
# strings (even when accessing the underlying hash directly):
|
|
32
|
+
#
|
|
33
|
+
# {'foo'=>'bar'}.hstore[:foo] # 'bar'
|
|
34
|
+
# {'foo'=>'bar'}.hstore.to_hash[:foo] # 'bar'
|
|
35
|
+
#
|
|
36
|
+
# HStore instances mostly just delegate to the underlying hash
|
|
37
|
+
# instance, so Hash methods that modify the receiver or returned
|
|
38
|
+
# modified copies of the receiver may not do string conversion.
|
|
39
|
+
# The following methods will handle string conversion, and more
|
|
40
|
+
# can be added later if desired:
|
|
41
|
+
#
|
|
42
|
+
# * \[\]
|
|
43
|
+
# * \[\]=
|
|
44
|
+
# * assoc (ruby 1.9 only)
|
|
45
|
+
# * delete
|
|
46
|
+
# * fetch
|
|
47
|
+
# * has_key?
|
|
48
|
+
# * has_value?
|
|
49
|
+
# * include?
|
|
50
|
+
# * key (ruby 1.9 only)
|
|
51
|
+
# * key?
|
|
52
|
+
# * member?
|
|
53
|
+
# * merge
|
|
54
|
+
# * merge!
|
|
55
|
+
# * rassoc (ruby 1.9 only)
|
|
56
|
+
# * replace
|
|
57
|
+
# * store
|
|
58
|
+
# * update
|
|
59
|
+
# * value?
|
|
60
|
+
#
|
|
61
|
+
# If you want to insert a hash into an hstore database column:
|
|
62
|
+
#
|
|
63
|
+
# DB[:table].insert(:column=>{'foo'=>'bar'}.hstore)
|
|
64
|
+
#
|
|
65
|
+
# If you would like to use hstore columns in your model objects, you
|
|
66
|
+
# probably want to modify the schema parsing/typecasting so that it
|
|
67
|
+
# recognizes and correctly handles the hstore columns, which you can
|
|
68
|
+
# do by:
|
|
69
|
+
#
|
|
70
|
+
# DB.extend Sequel::Postgres::HStore::DatabaseMethods
|
|
71
|
+
#
|
|
72
|
+
# If you are not using the native postgres adapter, you probably
|
|
73
|
+
# also want to use the typecast_on_load plugin in the model, and
|
|
74
|
+
# set it to typecast the hstore column(s) on load.
|
|
75
|
+
#
|
|
76
|
+
# This extension requires the delegate and strscan libraries.
|
|
77
|
+
|
|
78
|
+
require 'delegate'
|
|
79
|
+
require 'strscan'
|
|
80
|
+
|
|
81
|
+
module Sequel
|
|
82
|
+
module Postgres
|
|
83
|
+
class HStore < DelegateClass(Hash)
|
|
84
|
+
# Parser for PostgreSQL hstore output format.
|
|
85
|
+
class Parser < StringScanner
|
|
86
|
+
QUOTE_RE = /"/.freeze
|
|
87
|
+
KV_SEP_RE = /"\s*=>\s*/.freeze
|
|
88
|
+
NULL_RE = /NULL/.freeze
|
|
89
|
+
SEP_RE = /,\s*/.freeze
|
|
90
|
+
QUOTED_RE = /(\\"|[^"])*/.freeze
|
|
91
|
+
REPLACE_RE = /\\(.)/.freeze
|
|
92
|
+
REPLACE_WITH = '\1'.freeze
|
|
93
|
+
|
|
94
|
+
# Parse the output format that PostgreSQL uses for hstore
|
|
95
|
+
# columns. Note that this does not attempt to parse all
|
|
96
|
+
# input formats that PostgreSQL will accept. For instance,
|
|
97
|
+
# it expects all keys and non-NULL values to be quoted.
|
|
98
|
+
#
|
|
99
|
+
# Return the resulting hash of objects. This can be called
|
|
100
|
+
# multiple times, it will cache the parsed hash on the first
|
|
101
|
+
# call and use it for subsequent calls.
|
|
102
|
+
def parse
|
|
103
|
+
return @result if @result
|
|
104
|
+
hash = {}
|
|
105
|
+
while !eos?
|
|
106
|
+
skip(QUOTE_RE)
|
|
107
|
+
k = parse_quoted
|
|
108
|
+
skip(KV_SEP_RE)
|
|
109
|
+
if skip(QUOTE_RE)
|
|
110
|
+
v = parse_quoted
|
|
111
|
+
skip(QUOTE_RE)
|
|
112
|
+
else
|
|
113
|
+
scan(NULL_RE)
|
|
114
|
+
v = nil
|
|
115
|
+
end
|
|
116
|
+
skip(SEP_RE)
|
|
117
|
+
hash[k] = v
|
|
118
|
+
end
|
|
119
|
+
@result = hash
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Parse and unescape a quoted key/value.
|
|
125
|
+
def parse_quoted
|
|
126
|
+
scan(QUOTED_RE).gsub(REPLACE_RE, REPLACE_WITH)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
module DatabaseMethods
|
|
131
|
+
# Reset the conversion procs if using the native postgres adapter.
|
|
132
|
+
def self.extended(db)
|
|
133
|
+
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Handle hstores in bound variables
|
|
137
|
+
def bound_variable_arg(arg, conn)
|
|
138
|
+
case arg
|
|
139
|
+
when HStore
|
|
140
|
+
arg.unquoted_literal
|
|
141
|
+
when Hash
|
|
142
|
+
HStore.new(arg).unquoted_literal
|
|
143
|
+
else
|
|
144
|
+
super
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
# Recognize the hstore database type.
|
|
151
|
+
def schema_column_type(db_type)
|
|
152
|
+
db_type == 'hstore' ? :hstore : super
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Typecast value correctly to HStore. If already an
|
|
156
|
+
# HStore instance, return as is. If a hash, return
|
|
157
|
+
# an HStore version of it. If a string, assume it is
|
|
158
|
+
# in PostgreSQL output format and parse it using the
|
|
159
|
+
# parser.
|
|
160
|
+
def typecast_value_hstore(value)
|
|
161
|
+
case value
|
|
162
|
+
when HStore
|
|
163
|
+
value
|
|
164
|
+
when Hash
|
|
165
|
+
HStore.new(value)
|
|
166
|
+
when String
|
|
167
|
+
HStore.parse(value)
|
|
168
|
+
else
|
|
169
|
+
raise Sequel::InvalidValue, "invalid value for hstore: #{value.inspect}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Default proc used for all underlying HStore hashes, so that even
|
|
175
|
+
# if you grab the underlying hash, it will still convert non-string
|
|
176
|
+
# keys to strings during lookup.
|
|
177
|
+
DEFAULT_PROC = lambda{|h, k| h[k.to_s] unless k.is_a?(String)}
|
|
178
|
+
|
|
179
|
+
QUOTE = '"'.freeze
|
|
180
|
+
COMMA = ",".freeze
|
|
181
|
+
KV_SEP = "=>".freeze
|
|
182
|
+
NULL = "NULL".freeze
|
|
183
|
+
ESCAPE_RE = /("|\\)/.freeze
|
|
184
|
+
ESCAPE_REPLACE = '\\\\\1'.freeze
|
|
185
|
+
HSTORE_CAST = '::hstore'.freeze
|
|
186
|
+
|
|
187
|
+
# Parse the given string into an HStore, assuming the str is in PostgreSQL
|
|
188
|
+
# hstore output format.
|
|
189
|
+
def self.parse(str)
|
|
190
|
+
new(Parser.new(str).parse)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Override methods that accept key argument to convert to string.
|
|
194
|
+
(%w'[] delete has_key? include? key? member?' + Array((%w'assoc' if RUBY_VERSION >= '1.9.0'))).each do |m|
|
|
195
|
+
class_eval("def #{m}(k) super(k.to_s) end", __FILE__, __LINE__)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Override methods that accept value argument to convert to string unless nil.
|
|
199
|
+
(%w'has_value? value?' + Array((%w'key rassoc' if RUBY_VERSION >= '1.9.0'))).each do |m|
|
|
200
|
+
class_eval("def #{m}(v) super(convert_value(v)) end", __FILE__, __LINE__)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Override methods that accept key and value arguments to convert to string appropriately.
|
|
204
|
+
%w'[]= store'.each do |m|
|
|
205
|
+
class_eval("def #{m}(k, v) super(k.to_s, convert_value(v)) end", __FILE__, __LINE__)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Override methods that take hashes to convert the hashes to using strings for keys and
|
|
209
|
+
# values before using them.
|
|
210
|
+
%w'initialize merge! update replace'.each do |m|
|
|
211
|
+
class_eval("def #{m}(h, &block) super(convert_hash(h), &block) end", __FILE__, __LINE__)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Override to force the key argument to a string.
|
|
215
|
+
def fetch(key, *args, &block)
|
|
216
|
+
super(key.to_s, *args, &block)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Convert the input hash to string keys and values before merging,
|
|
220
|
+
# and return a new HStore instance with the merged hash.
|
|
221
|
+
def merge(hash, &block)
|
|
222
|
+
self.class.new(super(convert_hash(hash), &block))
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Return the underlying hash used by this HStore instance.
|
|
226
|
+
alias to_hash __getobj__
|
|
227
|
+
|
|
228
|
+
# Append a literalize version of the hstore to the sql.
|
|
229
|
+
def sql_literal_append(ds, sql)
|
|
230
|
+
ds.literal_append(sql, unquoted_literal)
|
|
231
|
+
sql << HSTORE_CAST
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Return a string containing the unquoted, unstring-escaped
|
|
235
|
+
# literal version of the hstore. Separated out for use by
|
|
236
|
+
# the bound argument code.
|
|
237
|
+
def unquoted_literal
|
|
238
|
+
str = ''
|
|
239
|
+
comma = false
|
|
240
|
+
commas = COMMA
|
|
241
|
+
quote = QUOTE
|
|
242
|
+
kv_sep = KV_SEP
|
|
243
|
+
null = NULL
|
|
244
|
+
each do |k, v|
|
|
245
|
+
str << commas if comma
|
|
246
|
+
str << quote << escape_value(k) << quote
|
|
247
|
+
str << kv_sep
|
|
248
|
+
if v.nil?
|
|
249
|
+
str << null
|
|
250
|
+
else
|
|
251
|
+
str << quote << escape_value(v) << quote
|
|
252
|
+
end
|
|
253
|
+
comma = true
|
|
254
|
+
end
|
|
255
|
+
str
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
private
|
|
259
|
+
|
|
260
|
+
# Return a new hash based on the input hash with string
|
|
261
|
+
# keys and string or nil values.
|
|
262
|
+
def convert_hash(h)
|
|
263
|
+
hash = Hash.new(&DEFAULT_PROC)
|
|
264
|
+
h.each{|k,v| hash[k.to_s] = convert_value(v)}
|
|
265
|
+
hash
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Return value v as a string unless it is already nil.
|
|
269
|
+
def convert_value(v)
|
|
270
|
+
v.to_s unless v.nil?
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Escape key/value strings when literalizing to
|
|
274
|
+
# correctly handle backslash and quote characters.
|
|
275
|
+
def escape_value(k)
|
|
276
|
+
k.to_s.gsub(ESCAPE_RE, ESCAPE_REPLACE)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
PG_NAMED_TYPES = {} unless defined?(PG_NAMED_TYPES)
|
|
281
|
+
# Associate the named types by default.
|
|
282
|
+
PG_NAMED_TYPES[:hstore] = HStore.method(:parse)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
class Hash
|
|
287
|
+
# Create a new HStore using the receiver as the input
|
|
288
|
+
# hash. Note that the HStore created will not use the
|
|
289
|
+
# receiver as the backing store, since it has to
|
|
290
|
+
# modify the hash. To get the new backing store, use:
|
|
291
|
+
#
|
|
292
|
+
# hash.hstore.to_hash
|
|
293
|
+
def hstore
|
|
294
|
+
Sequel::Postgres::HStore.new(self)
|
|
295
|
+
end
|
|
296
|
+
end
|