sequel 3.21.0 → 3.39.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 +1061 -3
- data/MIT-LICENSE +1 -1
- data/README.rdoc +103 -63
- data/Rakefile +59 -27
- data/bin/sequel +50 -26
- data/doc/active_record.rdoc +67 -51
- data/doc/advanced_associations.rdoc +189 -75
- data/doc/association_basics.rdoc +327 -79
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +10 -10
- data/doc/dataset_filtering.rdoc +54 -42
- data/doc/mass_assignment.rdoc +56 -0
- data/doc/migration.rdoc +89 -557
- data/doc/model_hooks.rdoc +80 -29
- data/doc/object_model.rdoc +547 -0
- data/doc/opening_databases.rdoc +114 -53
- data/doc/prepared_statements.rdoc +41 -15
- data/doc/querying.rdoc +137 -56
- data/doc/reflection.rdoc +36 -10
- 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/release_notes/3.37.0.txt +338 -0
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/release_notes/3.39.0.txt +237 -0
- data/doc/schema_modification.rdoc +585 -0
- data/doc/sharding.rdoc +99 -8
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +169 -0
- data/doc/thread_safety.rdoc +17 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +78 -43
- 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 +2 -61
- 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 +56 -0
- data/lib/sequel/adapters/jdbc/derby.rb +325 -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 +40 -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 +77 -52
- 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 +279 -97
- data/lib/sequel/adapters/mock.rb +372 -0
- data/lib/sequel/adapters/mysql.rb +125 -222
- data/lib/sequel/adapters/mysql2.rb +65 -56
- data/lib/sequel/adapters/odbc/mssql.rb +17 -9
- 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 +368 -138
- data/lib/sequel/adapters/shared/access.rb +32 -9
- data/lib/sequel/adapters/shared/db2.rb +343 -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 +401 -116
- data/lib/sequel/adapters/shared/mysql.rb +424 -89
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
- data/lib/sequel/adapters/shared/oracle.rb +257 -45
- data/lib/sequel/adapters/shared/postgres.rb +675 -279
- data/lib/sequel/adapters/shared/progress.rb +5 -8
- data/lib/sequel/adapters/shared/sqlite.rb +285 -96
- data/lib/sequel/adapters/sqlite.rb +101 -39
- data/lib/sequel/adapters/swift/mysql.rb +10 -12
- data/lib/sequel/adapters/swift/postgres.rb +13 -79
- data/lib/sequel/adapters/swift/sqlite.rb +9 -6
- data/lib/sequel/adapters/swift.rb +28 -19
- data/lib/sequel/adapters/tinytds.rb +174 -22
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
- data/lib/sequel/adapters/utils/pg_types.rb +81 -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 +33 -2
- data/lib/sequel/connection_pool/single.rb +5 -0
- data/lib/sequel/connection_pool/threaded.rb +43 -14
- data/lib/sequel/connection_pool.rb +10 -2
- data/lib/sequel/core.rb +143 -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 +190 -46
- data/lib/sequel/database/query.rb +272 -110
- data/lib/sequel/database/schema_generator.rb +103 -36
- data/lib/sequel/database/schema_methods.rb +300 -67
- data/lib/sequel/dataset/actions.rb +344 -90
- data/lib/sequel/dataset/features.rb +101 -8
- data/lib/sequel/dataset/graph.rb +42 -15
- data/lib/sequel/dataset/misc.rb +52 -25
- data/lib/sequel/dataset/mutation.rb +12 -7
- data/lib/sequel/dataset/prepared_statements.rb +68 -30
- data/lib/sequel/dataset/query.rb +305 -54
- data/lib/sequel/dataset/sql.rb +720 -292
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/_pretty_table.rb +83 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
- data/lib/sequel/extensions/blank.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +72 -0
- data/lib/sequel/extensions/constraint_validations.rb +451 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
- data/lib/sequel/extensions/eval_inspect.rb +173 -0
- data/lib/sequel/extensions/inflector.rb +4 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -4
- data/lib/sequel/extensions/migration.rb +100 -11
- data/lib/sequel/extensions/named_timezones.rb +9 -0
- data/lib/sequel/extensions/null_dataset.rb +94 -0
- data/lib/sequel/extensions/pagination.rb +4 -0
- data/lib/sequel/extensions/pg_array.rb +537 -0
- data/lib/sequel/extensions/pg_array_ops.rb +263 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
- data/lib/sequel/extensions/pg_hstore.rb +313 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
- data/lib/sequel/extensions/pg_inet.rb +113 -0
- data/lib/sequel/extensions/pg_interval.rb +191 -0
- data/lib/sequel/extensions/pg_json.rb +208 -0
- data/lib/sequel/extensions/pg_range.rb +512 -0
- data/lib/sequel/extensions/pg_range_ops.rb +150 -0
- data/lib/sequel/extensions/pg_row.rb +572 -0
- data/lib/sequel/extensions/pg_row_ops.rb +182 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
- data/lib/sequel/extensions/pretty_table.rb +17 -72
- data/lib/sequel/extensions/query.rb +8 -4
- 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 +282 -76
- data/lib/sequel/extensions/select_remove.rb +39 -0
- data/lib/sequel/extensions/server_block.rb +140 -0
- data/lib/sequel/extensions/split_array_nil.rb +65 -0
- data/lib/sequel/extensions/sql_expr.rb +8 -110
- data/lib/sequel/extensions/string_date_time.rb +4 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
- data/lib/sequel/extensions/to_dot.rb +99 -83
- data/lib/sequel/model/associations.rb +1263 -409
- data/lib/sequel/model/base.rb +624 -171
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +24 -2
- 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/constraint_validations.rb +198 -0
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +72 -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 +134 -15
- 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 +13 -2
- data/lib/sequel/plugins/many_through_many.rb +103 -51
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +150 -66
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -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 +31 -17
- data/lib/sequel/plugins/schema.rb +8 -3
- 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 +29 -3
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/tree.rb +3 -3
- 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/validation_helpers.rb +55 -4
- data/lib/sequel/plugins/xml_serializer.rb +15 -4
- data/lib/sequel/sql.rb +649 -122
- data/lib/sequel/timezones.rb +67 -40
- 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 +194 -66
- data/spec/adapters/mysql_spec.rb +475 -306
- data/spec/adapters/oracle_spec.rb +92 -117
- data/spec/adapters/postgres_spec.rb +1982 -325
- data/spec/adapters/spec_helper.rb +9 -6
- data/spec/adapters/sqlite_spec.rb +203 -75
- data/spec/core/connection_pool_spec.rb +218 -93
- data/spec/core/database_spec.rb +956 -522
- data/spec/core/dataset_spec.rb +1631 -1242
- data/spec/core/expression_filters_spec.rb +690 -334
- data/spec/core/mock_adapter_spec.rb +453 -0
- data/spec/core/object_graph_spec.rb +88 -129
- data/spec/core/schema_generator_spec.rb +4 -4
- data/spec/core/schema_spec.rb +556 -53
- data/spec/core/spec_helper.rb +7 -48
- data/spec/core_extensions_spec.rb +626 -0
- 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/boolean_readers_spec.rb +25 -25
- 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/extensions/constraint_validations_plugin_spec.rb +196 -0
- data/spec/extensions/constraint_validations_spec.rb +316 -0
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +88 -0
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/eval_inspect_spec.rb +67 -0
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +97 -128
- data/spec/extensions/identity_map_spec.rb +142 -24
- 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 -12
- data/spec/extensions/lazy_attributes_spec.rb +16 -20
- data/spec/extensions/list_spec.rb +49 -40
- data/spec/extensions/looser_typecasting_spec.rb +7 -7
- data/spec/extensions/many_through_many_spec.rb +368 -254
- 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 +268 -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 +112 -0
- data/spec/extensions/pg_array_spec.rb +320 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
- data/spec/extensions/pg_hstore_spec.rb +194 -0
- data/spec/extensions/pg_inet_spec.rb +47 -0
- data/spec/extensions/pg_interval_spec.rb +72 -0
- data/spec/extensions/pg_json_spec.rb +99 -0
- data/spec/extensions/pg_range_ops_spec.rb +56 -0
- data/spec/extensions/pg_range_spec.rb +395 -0
- data/spec/extensions/pg_row_ops_spec.rb +58 -0
- data/spec/extensions/pg_row_plugin_spec.rb +49 -0
- data/spec/extensions/pg_row_spec.rb +323 -0
- data/spec/extensions/pg_statement_cache_spec.rb +208 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -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 +167 -0
- data/spec/extensions/query_spec.rb +9 -3
- 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 +434 -49
- data/spec/extensions/schema_spec.rb +30 -49
- 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 +86 -48
- 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 +34 -67
- data/spec/extensions/split_array_nil_spec.rb +24 -0
- data/spec/extensions/sql_expr_spec.rb +29 -60
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/subclasses_spec.rb +14 -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 +8 -11
- 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 +58 -67
- data/spec/extensions/validation_helpers_spec.rb +31 -37
- 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 +11 -5
- data/spec/integration/dataset_test.rb +717 -129
- data/spec/integration/eager_loader_test.rb +38 -54
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +81 -2
- data/spec/integration/plugin_test.rb +604 -147
- data/spec/integration/prepared_statement_test.rb +272 -128
- data/spec/integration/schema_test.rb +367 -57
- data/spec/integration/spec_helper.rb +42 -32
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +183 -8
- data/spec/integration/type_test.rb +31 -8
- data/spec/model/association_reflection_spec.rb +207 -8
- data/spec/model/associations_spec.rb +1206 -693
- data/spec/model/base_spec.rb +334 -100
- data/spec/model/dataset_methods_spec.rb +45 -27
- data/spec/model/eager_loading_spec.rb +828 -692
- data/spec/model/hooks_spec.rb +355 -67
- data/spec/model/model_spec.rb +251 -179
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +506 -232
- data/spec/model/spec_helper.rb +16 -77
- data/spec/model/validations_spec.rb +38 -2
- data/spec/rcov.opts +2 -0
- metadata +209 -80
- data/spec/core/core_sql_spec.rb +0 -451
|
@@ -0,0 +1,313 @@
|
|
|
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, use Sequel.hstore:
|
|
19
|
+
#
|
|
20
|
+
# Sequel.hstore(hash)
|
|
21
|
+
#
|
|
22
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
|
23
|
+
# you can also use Hash#hstore:
|
|
24
|
+
#
|
|
25
|
+
# hash.hstore
|
|
26
|
+
#
|
|
27
|
+
# Since the hstore type only supports strings, non string keys and
|
|
28
|
+
# values are converted to strings
|
|
29
|
+
#
|
|
30
|
+
# {:foo=>1}.hstore.to_hash # {'foo'=>'1'}
|
|
31
|
+
# v = {}.hstore
|
|
32
|
+
# v[:foo] = 1
|
|
33
|
+
# v # {'foo'=>'1'}
|
|
34
|
+
#
|
|
35
|
+
# However, to make life easier, lookups by key are converted to
|
|
36
|
+
# strings (even when accessing the underlying hash directly):
|
|
37
|
+
#
|
|
38
|
+
# {'foo'=>'bar'}.hstore[:foo] # 'bar'
|
|
39
|
+
# {'foo'=>'bar'}.hstore.to_hash[:foo] # 'bar'
|
|
40
|
+
#
|
|
41
|
+
# HStore instances mostly just delegate to the underlying hash
|
|
42
|
+
# instance, so Hash methods that modify the receiver or returned
|
|
43
|
+
# modified copies of the receiver may not do string conversion.
|
|
44
|
+
# The following methods will handle string conversion, and more
|
|
45
|
+
# can be added later if desired:
|
|
46
|
+
#
|
|
47
|
+
# * \[\]
|
|
48
|
+
# * \[\]=
|
|
49
|
+
# * assoc (ruby 1.9 only)
|
|
50
|
+
# * delete
|
|
51
|
+
# * fetch
|
|
52
|
+
# * has_key?
|
|
53
|
+
# * has_value?
|
|
54
|
+
# * include?
|
|
55
|
+
# * key (ruby 1.9 only)
|
|
56
|
+
# * key?
|
|
57
|
+
# * member?
|
|
58
|
+
# * merge
|
|
59
|
+
# * merge!
|
|
60
|
+
# * rassoc (ruby 1.9 only)
|
|
61
|
+
# * replace
|
|
62
|
+
# * store
|
|
63
|
+
# * update
|
|
64
|
+
# * value?
|
|
65
|
+
#
|
|
66
|
+
# If you want to insert a hash into an hstore database column:
|
|
67
|
+
#
|
|
68
|
+
# DB[:table].insert(:column=>{'foo'=>'bar'}.hstore)
|
|
69
|
+
#
|
|
70
|
+
# If you would like to use hstore columns in your model objects, you
|
|
71
|
+
# probably want to modify the schema parsing/typecasting so that it
|
|
72
|
+
# recognizes and correctly handles the hstore columns, which you can
|
|
73
|
+
# do by:
|
|
74
|
+
#
|
|
75
|
+
# DB.extension :pg_hstore
|
|
76
|
+
#
|
|
77
|
+
# If you are not using the native postgres adapter, you probably
|
|
78
|
+
# also want to use the typecast_on_load plugin in the model, and
|
|
79
|
+
# set it to typecast the hstore column(s) on load.
|
|
80
|
+
#
|
|
81
|
+
# This extension requires the delegate and strscan libraries.
|
|
82
|
+
|
|
83
|
+
require 'delegate'
|
|
84
|
+
require 'strscan'
|
|
85
|
+
|
|
86
|
+
module Sequel
|
|
87
|
+
module Postgres
|
|
88
|
+
class HStore < DelegateClass(Hash)
|
|
89
|
+
# Parser for PostgreSQL hstore output format.
|
|
90
|
+
class Parser < StringScanner
|
|
91
|
+
QUOTE_RE = /"/.freeze
|
|
92
|
+
KV_SEP_RE = /"\s*=>\s*/.freeze
|
|
93
|
+
NULL_RE = /NULL/.freeze
|
|
94
|
+
SEP_RE = /,\s*/.freeze
|
|
95
|
+
QUOTED_RE = /(\\"|[^"])*/.freeze
|
|
96
|
+
REPLACE_RE = /\\(.)/.freeze
|
|
97
|
+
REPLACE_WITH = '\1'.freeze
|
|
98
|
+
|
|
99
|
+
# Parse the output format that PostgreSQL uses for hstore
|
|
100
|
+
# columns. Note that this does not attempt to parse all
|
|
101
|
+
# input formats that PostgreSQL will accept. For instance,
|
|
102
|
+
# it expects all keys and non-NULL values to be quoted.
|
|
103
|
+
#
|
|
104
|
+
# Return the resulting hash of objects. This can be called
|
|
105
|
+
# multiple times, it will cache the parsed hash on the first
|
|
106
|
+
# call and use it for subsequent calls.
|
|
107
|
+
def parse
|
|
108
|
+
return @result if @result
|
|
109
|
+
hash = {}
|
|
110
|
+
while !eos?
|
|
111
|
+
skip(QUOTE_RE)
|
|
112
|
+
k = parse_quoted
|
|
113
|
+
skip(KV_SEP_RE)
|
|
114
|
+
if skip(QUOTE_RE)
|
|
115
|
+
v = parse_quoted
|
|
116
|
+
skip(QUOTE_RE)
|
|
117
|
+
else
|
|
118
|
+
scan(NULL_RE)
|
|
119
|
+
v = nil
|
|
120
|
+
end
|
|
121
|
+
skip(SEP_RE)
|
|
122
|
+
hash[k] = v
|
|
123
|
+
end
|
|
124
|
+
@result = hash
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# Parse and unescape a quoted key/value.
|
|
130
|
+
def parse_quoted
|
|
131
|
+
scan(QUOTED_RE).gsub(REPLACE_RE, REPLACE_WITH)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
module DatabaseMethods
|
|
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
|
+
else
|
|
167
|
+
raise Sequel::InvalidValue, "invalid value for hstore: #{value.inspect}"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Default proc used for all underlying HStore hashes, so that even
|
|
173
|
+
# if you grab the underlying hash, it will still convert non-string
|
|
174
|
+
# keys to strings during lookup.
|
|
175
|
+
DEFAULT_PROC = lambda{|h, k| h[k.to_s] unless k.is_a?(String)}
|
|
176
|
+
|
|
177
|
+
QUOTE = '"'.freeze
|
|
178
|
+
COMMA = ",".freeze
|
|
179
|
+
KV_SEP = "=>".freeze
|
|
180
|
+
NULL = "NULL".freeze
|
|
181
|
+
ESCAPE_RE = /("|\\)/.freeze
|
|
182
|
+
ESCAPE_REPLACE = '\\\\\1'.freeze
|
|
183
|
+
HSTORE_CAST = '::hstore'.freeze
|
|
184
|
+
|
|
185
|
+
# Parse the given string into an HStore, assuming the str is in PostgreSQL
|
|
186
|
+
# hstore output format.
|
|
187
|
+
def self.parse(str)
|
|
188
|
+
new(Parser.new(str).parse)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Override methods that accept key argument to convert to string.
|
|
192
|
+
(%w'[] delete has_key? include? key? member?' + Array((%w'assoc' if RUBY_VERSION >= '1.9.0'))).each do |m|
|
|
193
|
+
class_eval("def #{m}(k) super(k.to_s) end", __FILE__, __LINE__)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Override methods that accept value argument to convert to string unless nil.
|
|
197
|
+
(%w'has_value? value?' + Array((%w'key rassoc' if RUBY_VERSION >= '1.9.0'))).each do |m|
|
|
198
|
+
class_eval("def #{m}(v) super(convert_value(v)) end", __FILE__, __LINE__)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Override methods that accept key and value arguments to convert to string appropriately.
|
|
202
|
+
%w'[]= store'.each do |m|
|
|
203
|
+
class_eval("def #{m}(k, v) super(k.to_s, convert_value(v)) end", __FILE__, __LINE__)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Override methods that take hashes to convert the hashes to using strings for keys and
|
|
207
|
+
# values before using them.
|
|
208
|
+
%w'initialize merge! update replace'.each do |m|
|
|
209
|
+
class_eval("def #{m}(h, &block) super(convert_hash(h), &block) end", __FILE__, __LINE__)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Override to force the key argument to a string.
|
|
213
|
+
def fetch(key, *args, &block)
|
|
214
|
+
super(key.to_s, *args, &block)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Convert the input hash to string keys and values before merging,
|
|
218
|
+
# and return a new HStore instance with the merged hash.
|
|
219
|
+
def merge(hash, &block)
|
|
220
|
+
self.class.new(super(convert_hash(hash), &block))
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Return the underlying hash used by this HStore instance.
|
|
224
|
+
alias to_hash __getobj__
|
|
225
|
+
|
|
226
|
+
# Append a literalize version of the hstore to the sql.
|
|
227
|
+
def sql_literal_append(ds, sql)
|
|
228
|
+
ds.literal_append(sql, unquoted_literal)
|
|
229
|
+
sql << HSTORE_CAST
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Return a string containing the unquoted, unstring-escaped
|
|
233
|
+
# literal version of the hstore. Separated out for use by
|
|
234
|
+
# the bound argument code.
|
|
235
|
+
def unquoted_literal
|
|
236
|
+
str = ''
|
|
237
|
+
comma = false
|
|
238
|
+
commas = COMMA
|
|
239
|
+
quote = QUOTE
|
|
240
|
+
kv_sep = KV_SEP
|
|
241
|
+
null = NULL
|
|
242
|
+
each do |k, v|
|
|
243
|
+
str << commas if comma
|
|
244
|
+
str << quote << escape_value(k) << quote
|
|
245
|
+
str << kv_sep
|
|
246
|
+
if v.nil?
|
|
247
|
+
str << null
|
|
248
|
+
else
|
|
249
|
+
str << quote << escape_value(v) << quote
|
|
250
|
+
end
|
|
251
|
+
comma = true
|
|
252
|
+
end
|
|
253
|
+
str
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
private
|
|
257
|
+
|
|
258
|
+
# Return a new hash based on the input hash with string
|
|
259
|
+
# keys and string or nil values.
|
|
260
|
+
def convert_hash(h)
|
|
261
|
+
hash = Hash.new(&DEFAULT_PROC)
|
|
262
|
+
h.each{|k,v| hash[k.to_s] = convert_value(v)}
|
|
263
|
+
hash
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Return value v as a string unless it is already nil.
|
|
267
|
+
def convert_value(v)
|
|
268
|
+
v.to_s unless v.nil?
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Escape key/value strings when literalizing to
|
|
272
|
+
# correctly handle backslash and quote characters.
|
|
273
|
+
def escape_value(k)
|
|
274
|
+
k.to_s.gsub(ESCAPE_RE, ESCAPE_REPLACE)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
PG_NAMED_TYPES = {} unless defined?(PG_NAMED_TYPES)
|
|
279
|
+
# Associate the named types by default.
|
|
280
|
+
PG_NAMED_TYPES[:hstore] = HStore.method(:parse)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
module SQL::Builders
|
|
284
|
+
# Return a Postgres::HStore proxy for the given hash.
|
|
285
|
+
def hstore(v)
|
|
286
|
+
case v
|
|
287
|
+
when Postgres::HStore
|
|
288
|
+
v
|
|
289
|
+
when Hash
|
|
290
|
+
Postgres::HStore.new(v)
|
|
291
|
+
else
|
|
292
|
+
# May not be defined unless the pg_hstore_ops extension is used
|
|
293
|
+
hstore_op(v)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
Database.register_extension(:pg_hstore, Postgres::HStore::DatabaseMethods)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
if Sequel.core_extensions?
|
|
302
|
+
class Hash
|
|
303
|
+
# Create a new HStore using the receiver as the input
|
|
304
|
+
# hash. Note that the HStore created will not use the
|
|
305
|
+
# receiver as the backing store, since it has to
|
|
306
|
+
# modify the hash. To get the new backing store, use:
|
|
307
|
+
#
|
|
308
|
+
# hash.hstore.to_hash
|
|
309
|
+
def hstore
|
|
310
|
+
Sequel::Postgres::HStore.new(self)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# The pg_hstore_ops extension adds support to Sequel's DSL to make
|
|
2
|
+
# it easier to call PostgreSQL hstore functions and operators.
|
|
3
|
+
#
|
|
4
|
+
# To load the extension:
|
|
5
|
+
#
|
|
6
|
+
# Sequel.extension :pg_hstore_ops
|
|
7
|
+
#
|
|
8
|
+
# The most common usage is taking an object that represents an SQL
|
|
9
|
+
# expression (such as a :symbol), and calling Sequel.hstore_op with it:
|
|
10
|
+
#
|
|
11
|
+
# h = Sequel.hstore_op(:hstore_column)
|
|
12
|
+
#
|
|
13
|
+
# If you have also loaded the pg_hstore extension, you can use
|
|
14
|
+
# Sequel.hstore as well:
|
|
15
|
+
#
|
|
16
|
+
# h = Sequel.hstore(:hstore_column)
|
|
17
|
+
#
|
|
18
|
+
# Also, on most Sequel expression objects, you can call the hstore
|
|
19
|
+
# method:
|
|
20
|
+
#
|
|
21
|
+
# h = Sequel.expr(:hstore_column).hstore
|
|
22
|
+
#
|
|
23
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
|
24
|
+
# you can also call Symbol#hstore:
|
|
25
|
+
#
|
|
26
|
+
# h = :hstore_column.hstore
|
|
27
|
+
#
|
|
28
|
+
# This creates a Sequel::Postgres::HStoreOp object that can be used
|
|
29
|
+
# for easier querying:
|
|
30
|
+
#
|
|
31
|
+
# h - 'a' # hstore_column - 'a'
|
|
32
|
+
# h['a'] # hstore_column -> 'a'
|
|
33
|
+
#
|
|
34
|
+
# h.concat(:other_hstore_column) # ||
|
|
35
|
+
# h.has_key?('a') # ?
|
|
36
|
+
# h.contain_all(:array_column) # ?&
|
|
37
|
+
# h.contain_any(:array_column) # ?|
|
|
38
|
+
# h.contains(:other_hstore_column) # @>
|
|
39
|
+
# h.contained_by(:other_hstore_column) # <@
|
|
40
|
+
#
|
|
41
|
+
# h.defined # defined(hstore_column)
|
|
42
|
+
# h.delete('a') # delete(hstore_column, 'a')
|
|
43
|
+
# h.each # each(hstore_column)
|
|
44
|
+
# h.keys # akeys(hstore_column)
|
|
45
|
+
# h.populate(:a) # populate_record(a, hstore_column)
|
|
46
|
+
# h.record_set(:a) # (a #= hstore_column)
|
|
47
|
+
# h.skeys # skeys(hstore_column)
|
|
48
|
+
# h.slice(:a) # slice(hstore_column, a)
|
|
49
|
+
# h.svals # svals(hstore_column)
|
|
50
|
+
# h.to_array # hstore_to_array(hstore_column)
|
|
51
|
+
# h.to_matrix # hstore_to_matrix(hstore_column)
|
|
52
|
+
# h.values # avals(hstore_column)
|
|
53
|
+
#
|
|
54
|
+
# See the PostgreSQL hstore function and operator documentation for more
|
|
55
|
+
# details on what these functions and operators do.
|
|
56
|
+
#
|
|
57
|
+
# If you are also using the pg_hstore extension, you should load it before
|
|
58
|
+
# loading this extension. Doing so will allow you to use HStore#op to get
|
|
59
|
+
# an HStoreOp, allowing you to perform hstore operations on hstore literals.
|
|
60
|
+
|
|
61
|
+
module Sequel
|
|
62
|
+
module Postgres
|
|
63
|
+
# The HStoreOp class is a simple container for a single object that
|
|
64
|
+
# defines methods that yield Sequel expression objects representing
|
|
65
|
+
# PostgreSQL hstore operators and functions.
|
|
66
|
+
#
|
|
67
|
+
# In the method documentation examples, assume that:
|
|
68
|
+
#
|
|
69
|
+
# hstore_op = :hstore.hstore
|
|
70
|
+
class HStoreOp < Sequel::SQL::Wrapper
|
|
71
|
+
CONCAT = ["(".freeze, " || ".freeze, ")".freeze].freeze
|
|
72
|
+
CONTAIN_ALL = ["(".freeze, " ?& ".freeze, ")".freeze].freeze
|
|
73
|
+
CONTAIN_ANY = ["(".freeze, " ?| ".freeze, ")".freeze].freeze
|
|
74
|
+
CONTAINS = ["(".freeze, " @> ".freeze, ")".freeze].freeze
|
|
75
|
+
CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
|
|
76
|
+
HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
|
|
77
|
+
LOOKUP = ["(".freeze, " -> ".freeze, ")".freeze].freeze
|
|
78
|
+
RECORD_SET = ["(".freeze, " #= ".freeze, ")".freeze].freeze
|
|
79
|
+
|
|
80
|
+
# Delete entries from an hstore using the subtraction operator:
|
|
81
|
+
#
|
|
82
|
+
# hstore_op - 'a' # (hstore - 'a')
|
|
83
|
+
def -(other)
|
|
84
|
+
HStoreOp.new(super)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Lookup the value for the given key in an hstore:
|
|
88
|
+
#
|
|
89
|
+
# hstore_op['a'] # (hstore -> 'a')
|
|
90
|
+
def [](key)
|
|
91
|
+
Sequel::SQL::StringExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, key]))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Check if the receiver contains all of the keys in the given array:
|
|
95
|
+
#
|
|
96
|
+
# hstore_op.contain_all(:a) # (hstore ?& a)
|
|
97
|
+
def contain_all(other)
|
|
98
|
+
bool_op(CONTAIN_ALL, other)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Check if the receiver contains any of the keys in the given array:
|
|
102
|
+
#
|
|
103
|
+
# hstore_op.contain_any(:a) # (hstore ?| a)
|
|
104
|
+
def contain_any(other)
|
|
105
|
+
bool_op(CONTAIN_ANY, other)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if the receiver contains all entries in the other hstore:
|
|
109
|
+
#
|
|
110
|
+
# hstore_op.contains(:h) # (hstore @> h)
|
|
111
|
+
def contains(other)
|
|
112
|
+
bool_op(CONTAINS, other)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check if the other hstore contains all entries in the receiver:
|
|
116
|
+
#
|
|
117
|
+
# hstore_op.contained_by(:h) # (hstore <@ h)
|
|
118
|
+
def contained_by(other)
|
|
119
|
+
bool_op(CONTAINED_BY, other)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Check if the receiver contains a non-NULL value for the given key:
|
|
123
|
+
#
|
|
124
|
+
# hstore_op.defined('a') # defined(hstore, 'a')
|
|
125
|
+
def defined(key)
|
|
126
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, function(:defined, key))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Delete the matching entries from the receiver:
|
|
130
|
+
#
|
|
131
|
+
# hstore_op.delete('a') # delete(hstore, 'a')
|
|
132
|
+
def delete(key)
|
|
133
|
+
HStoreOp.new(function(:delete, key))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Transform the receiver into a set of keys and values:
|
|
137
|
+
#
|
|
138
|
+
# hstore_op.each # each(hstore)
|
|
139
|
+
def each
|
|
140
|
+
function(:each)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check if the receiver contains the given key:
|
|
144
|
+
#
|
|
145
|
+
# hstore_op.has_key?('a') # (hstore ? 'a')
|
|
146
|
+
def has_key?(key)
|
|
147
|
+
bool_op(HAS_KEY, key)
|
|
148
|
+
end
|
|
149
|
+
alias include? has_key?
|
|
150
|
+
alias key? has_key?
|
|
151
|
+
alias member? has_key?
|
|
152
|
+
alias exist? has_key?
|
|
153
|
+
|
|
154
|
+
# Return the receiver.
|
|
155
|
+
def hstore
|
|
156
|
+
self
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Return the keys as a PostgreSQL array:
|
|
160
|
+
#
|
|
161
|
+
# hstore_op.keys # akeys(hstore)
|
|
162
|
+
def keys
|
|
163
|
+
function(:akeys)
|
|
164
|
+
end
|
|
165
|
+
alias akeys keys
|
|
166
|
+
|
|
167
|
+
# Merge a given hstore into the receiver:
|
|
168
|
+
#
|
|
169
|
+
# hstore_op.merge(:a) # (hstore || a)
|
|
170
|
+
def merge(other)
|
|
171
|
+
HStoreOp.new(Sequel::SQL::PlaceholderLiteralString.new(CONCAT, [self, other]))
|
|
172
|
+
end
|
|
173
|
+
alias concat merge
|
|
174
|
+
|
|
175
|
+
# Create a new record populated with entries from the receiver:
|
|
176
|
+
#
|
|
177
|
+
# hstore_op.populate(:a) # populate_record(a, hstore)
|
|
178
|
+
def populate(record)
|
|
179
|
+
SQL::Function.new(:populate_record, record, self)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Update the values in a record using entries in the receiver:
|
|
183
|
+
#
|
|
184
|
+
# hstore_op.record_set(:a) # (a #= hstore)
|
|
185
|
+
def record_set(record)
|
|
186
|
+
Sequel::SQL::PlaceholderLiteralString.new(RECORD_SET, [record, value])
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Return the keys as a PostgreSQL set:
|
|
190
|
+
#
|
|
191
|
+
# hstore_op.skeys # skeys(hstore)
|
|
192
|
+
def skeys
|
|
193
|
+
function(:skeys)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Return an hstore with only the keys in the given array:
|
|
197
|
+
#
|
|
198
|
+
# hstore_op.slice(:a) # slice(hstore, a)
|
|
199
|
+
def slice(keys)
|
|
200
|
+
HStoreOp.new(function(:slice, keys))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Return the values as a PostgreSQL set:
|
|
204
|
+
#
|
|
205
|
+
# hstore_op.svals # svals(hstore)
|
|
206
|
+
def svals
|
|
207
|
+
function(:svals)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Return a flattened array of the receiver with alternating
|
|
211
|
+
# keys and values:
|
|
212
|
+
#
|
|
213
|
+
# hstore_op.to_array # hstore_to_array(hstore)
|
|
214
|
+
def to_array
|
|
215
|
+
function(:hstore_to_array)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Return a nested array of the receiver, with arrays of
|
|
219
|
+
# 2 element (key/value) arrays:
|
|
220
|
+
#
|
|
221
|
+
# hstore_op.to_matrix # hstore_to_matrix(hstore)
|
|
222
|
+
def to_matrix
|
|
223
|
+
function(:hstore_to_matrix)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Return the values as a PostgreSQL array:
|
|
227
|
+
#
|
|
228
|
+
# hstore_op.values # avals(hstore)
|
|
229
|
+
def values
|
|
230
|
+
function(:avals)
|
|
231
|
+
end
|
|
232
|
+
alias avals values
|
|
233
|
+
|
|
234
|
+
private
|
|
235
|
+
|
|
236
|
+
# Return a placeholder literal with the given str and args, wrapped
|
|
237
|
+
# in a boolean expression, used by operators that return booleans.
|
|
238
|
+
def bool_op(str, other)
|
|
239
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Return a function with the given name, and the receiver as the first
|
|
243
|
+
# argument, with any additional arguments given.
|
|
244
|
+
def function(name, *args)
|
|
245
|
+
SQL::Function.new(name, self, *args)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
module HStoreOpMethods
|
|
250
|
+
# Wrap the receiver in an HStoreOp so you can easily use the PostgreSQL
|
|
251
|
+
# hstore functions and operators with it.
|
|
252
|
+
def hstore
|
|
253
|
+
HStoreOp.new(self)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
if defined?(HStore)
|
|
258
|
+
class HStore
|
|
259
|
+
# Wrap the receiver in an HStoreOp so you can easily use the PostgreSQL
|
|
260
|
+
# hstore functions and operators with it.
|
|
261
|
+
def op
|
|
262
|
+
HStoreOp.new(self)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
module SQL::Builders
|
|
269
|
+
# Return the object wrapped in an Postgres::HStoreOp.
|
|
270
|
+
def hstore_op(v)
|
|
271
|
+
case v
|
|
272
|
+
when Postgres::HStoreOp
|
|
273
|
+
v
|
|
274
|
+
else
|
|
275
|
+
Postgres::HStoreOp.new(v)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
class SQL::GenericExpression
|
|
281
|
+
include Sequel::Postgres::HStoreOpMethods
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
class LiteralString
|
|
285
|
+
include Sequel::Postgres::HStoreOpMethods
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
if Sequel.core_extensions?
|
|
290
|
+
class Symbol
|
|
291
|
+
include Sequel::Postgres::HStoreOpMethods
|
|
292
|
+
end
|
|
293
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# The pg_inet extension adds support for Sequel to handle
|
|
2
|
+
# PostgreSQL's inet and cidr types using ruby's IPAddr class.
|
|
3
|
+
#
|
|
4
|
+
# This extension integrates with Sequel's native postgres adapter, so
|
|
5
|
+
# that when inet/cidr fields are retrieved, they are returned as
|
|
6
|
+
# IPAddr instances
|
|
7
|
+
#
|
|
8
|
+
# After loading the extension, you should extend your dataset
|
|
9
|
+
# with a module so that it correctly handles the inet/cidr type:
|
|
10
|
+
#
|
|
11
|
+
# DB.extension :pg_inet
|
|
12
|
+
#
|
|
13
|
+
# If you are not using the native postgres adapter, you probably
|
|
14
|
+
# also want to use the typecast_on_load plugin in the model, and
|
|
15
|
+
# set it to typecast the inet/cidr column(s) on load.
|
|
16
|
+
#
|
|
17
|
+
# This extension integrates with the pg_array extension. If you plan
|
|
18
|
+
# to use the inet[] or cidr[] types, load the pg_array extension before
|
|
19
|
+
# the pg_inet extension:
|
|
20
|
+
#
|
|
21
|
+
# DB.extension :pg_array, :pg_inet
|
|
22
|
+
#
|
|
23
|
+
# This extension does not add special support for the macaddr
|
|
24
|
+
# type. Ruby doesn't have a stdlib class that represents mac
|
|
25
|
+
# addresses, so these will still be returned as strings. The exception
|
|
26
|
+
# to this is that the pg_array extension integration will recognize
|
|
27
|
+
# macaddr[] types return them as arrays of strings.
|
|
28
|
+
|
|
29
|
+
require 'ipaddr'
|
|
30
|
+
Sequel.require 'adapters/utils/pg_types'
|
|
31
|
+
|
|
32
|
+
module Sequel
|
|
33
|
+
module Postgres
|
|
34
|
+
# Methods enabling Database object integration with the inet/cidr types.
|
|
35
|
+
module InetDatabaseMethods
|
|
36
|
+
|
|
37
|
+
# Reset the conversion procs when extending the Database object, so
|
|
38
|
+
# it will pick up the inet/cidr converter. Also, extend the datasets
|
|
39
|
+
# with support for literalizing the IPAddr types.
|
|
40
|
+
def self.extended(db)
|
|
41
|
+
db.extend_datasets(InetDatasetMethods)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convert an IPAddr arg to a string. Probably not necessary, but done
|
|
45
|
+
# for safety.
|
|
46
|
+
def bound_variable_arg(arg, conn)
|
|
47
|
+
case arg
|
|
48
|
+
when IPAddr
|
|
49
|
+
"#{arg.to_s}/#{arg.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
|
|
50
|
+
else
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Make the column type detection recognize the inet and cidr types.
|
|
56
|
+
def schema_column_type(db_type)
|
|
57
|
+
case db_type
|
|
58
|
+
when 'inet', 'cidr'
|
|
59
|
+
:ipaddr
|
|
60
|
+
else
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Handle inet[]/cidr[] types in bound variables.
|
|
68
|
+
def bound_variable_array(a)
|
|
69
|
+
case a
|
|
70
|
+
when IPAddr
|
|
71
|
+
"\"#{a.to_s}/#{a.instance_variable_get(:@mask_addr).to_s(2).count('1')}\""
|
|
72
|
+
else
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Typecast the given value to an IPAddr object.
|
|
78
|
+
def typecast_value_ipaddr(value)
|
|
79
|
+
case value
|
|
80
|
+
when IPAddr
|
|
81
|
+
value
|
|
82
|
+
when String
|
|
83
|
+
IPAddr.new(value)
|
|
84
|
+
else
|
|
85
|
+
raise Sequel::InvalidValue, "invalid value for inet/cidr: #{value.inspect}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
module InetDatasetMethods
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# Convert IPAddr value to a string and append a literal version
|
|
94
|
+
# of the string to the sql.
|
|
95
|
+
def literal_other_append(sql, value)
|
|
96
|
+
if value.is_a?(IPAddr)
|
|
97
|
+
literal_string_append(sql, "#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}")
|
|
98
|
+
else
|
|
99
|
+
super
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
PG_TYPES[869] = PG_TYPES[650] = IPAddr.method(:new)
|
|
105
|
+
if defined?(PGArray) && PGArray.respond_to?(:register)
|
|
106
|
+
PGArray.register('inet', :oid=>1041, :scalar_oid=>869)
|
|
107
|
+
PGArray.register('cidr', :oid=>651, :scalar_oid=>650)
|
|
108
|
+
PGArray.register('macaddr', :oid=>1040)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
Database.register_extension(:pg_inet, Postgres::InetDatabaseMethods)
|
|
113
|
+
end
|