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,323 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
|
+
|
|
3
|
+
describe "pg_row extension" do
|
|
4
|
+
before do
|
|
5
|
+
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
|
6
|
+
@db.extension(:pg_array, :pg_row)
|
|
7
|
+
@m = Sequel::Postgres::PGRow
|
|
8
|
+
@db.sqls
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should parse record objects as arrays" do
|
|
12
|
+
a = Sequel::Postgres::PG_TYPES[2249].call("(a,b,c)")
|
|
13
|
+
a.should be_a_kind_of(@m::ArrayRow)
|
|
14
|
+
a.to_a.should be_a_kind_of(Array)
|
|
15
|
+
a[0].should == 'a'
|
|
16
|
+
a.should == %w'a b c'
|
|
17
|
+
a.db_type.should == nil
|
|
18
|
+
@db.literal(a).should == "ROW('a', 'b', 'c')"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should parse arrays of record objects as arrays of arrays" do
|
|
22
|
+
as = Sequel::Postgres::PG_TYPES[2287].call('{"(a,b,c)","(d,e,f)"}')
|
|
23
|
+
as.should == [%w'a b c', %w'd e f']
|
|
24
|
+
as.each do |a|
|
|
25
|
+
a.should be_a_kind_of(@m::ArrayRow)
|
|
26
|
+
a.to_a.should be_a_kind_of(Array)
|
|
27
|
+
a.db_type.should == nil
|
|
28
|
+
end
|
|
29
|
+
@db.literal(as).should == "ARRAY[ROW('a', 'b', 'c'),ROW('d', 'e', 'f')]::record[]"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should be able to register custom parsing of row types as array-like objects" do
|
|
33
|
+
klass = @m::ArrayRow.subclass(:foo)
|
|
34
|
+
parser = @m::Parser.new(:converter=>klass)
|
|
35
|
+
a = parser.call("(a,b,c)")
|
|
36
|
+
a.should be_a_kind_of(klass)
|
|
37
|
+
a.to_a.should be_a_kind_of(Array)
|
|
38
|
+
a[0].should == 'a'
|
|
39
|
+
a.should == %w'a b c'
|
|
40
|
+
a.db_type.should == :foo
|
|
41
|
+
@db.literal(a).should == "ROW('a', 'b', 'c')::foo"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "should be able to register custom parsing of row types as hash-like objects" do
|
|
45
|
+
klass = @m::HashRow.subclass(:foo, [:a, :b, :c])
|
|
46
|
+
parser = @m::Parser.new(:converter=>klass, :columns=>[:a, :b, :c])
|
|
47
|
+
a = parser.call("(a,b,c)")
|
|
48
|
+
a.should be_a_kind_of(klass)
|
|
49
|
+
a.to_hash.should be_a_kind_of(Hash)
|
|
50
|
+
a[:a].should == 'a'
|
|
51
|
+
a.should == {:a=>'a', :b=>'b', :c=>'c'}
|
|
52
|
+
a.db_type.should == :foo
|
|
53
|
+
a.columns.should == [:a, :b, :c]
|
|
54
|
+
@db.literal(a).should == "ROW('a', 'b', 'c')::foo"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "should raise an error if attempting to literalize a HashRow without column information" do
|
|
58
|
+
h = @m::HashRow.call(:a=>'a', :b=>'b', :c=>'c')
|
|
59
|
+
proc{@db.literal(h)}.should raise_error(Sequel::Error)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should be able to manually override db_type per ArrayRow instance" do
|
|
63
|
+
a = @m::ArrayRow.call(%w'a b c')
|
|
64
|
+
a.db_type = :foo
|
|
65
|
+
@db.literal(a).should == "ROW('a', 'b', 'c')::foo"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should be able to manually override db_type and columns per HashRow instance" do
|
|
69
|
+
h = @m::HashRow.call(:a=>'a', :c=>'c', :b=>'b')
|
|
70
|
+
h.db_type = :foo
|
|
71
|
+
h.columns = [:a, :b, :c]
|
|
72
|
+
@db.literal(h).should == "ROW('a', 'b', 'c')::foo"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should correctly split an empty row" do
|
|
76
|
+
@m::Splitter.new("()").parse.should == [nil]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should correctly split a row with a single value" do
|
|
80
|
+
@m::Splitter.new("(1)").parse.should == %w'1'
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should correctly split a row with multiple values" do
|
|
84
|
+
@m::Splitter.new("(1,2)").parse.should == %w'1 2'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "should correctly NULL values when splitting" do
|
|
88
|
+
@m::Splitter.new("(1,)").parse.should == ['1', nil]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "should correctly empty string values when splitting" do
|
|
92
|
+
@m::Splitter.new('(1,"")').parse.should == ['1', '']
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should handle quoted values when splitting" do
|
|
96
|
+
@m::Splitter.new('("1","2")').parse.should == %w'1 2'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should handle escaped backslashes in quoted values when splitting" do
|
|
100
|
+
@m::Splitter.new('("\\\\1","2\\\\")').parse.should == ['\\1', '2\\']
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "should handle doubled quotes in quoted values when splitting" do
|
|
104
|
+
@m::Splitter.new('("""1","2""")').parse.should == ['"1', '2"']
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "should correctly convert types when parsing into an array" do
|
|
108
|
+
@m::Parser.new(:column_converters=>[proc{|s| s*2}, proc{|s| s*3}, proc{|s| s*4}]).call("(a,b,c)").should == %w'aa bbb cccc'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "should correctly convert types into hashes if columns are known" do
|
|
112
|
+
@m::Parser.new(:columns=>[:a, :b, :c]).call("(a,b,c)").should == {:a=>'a', :b=>'b', :c=>'c'}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "should correctly handle type conversion when converting into hashes" do
|
|
116
|
+
@m::Parser.new(:column_converters=>[proc{|s| s*2}, proc{|s| s*3}, proc{|s| s*4}], :columns=>[:a, :b, :c]).call("(a,b,c)").should == {:a=>'aa', :b=>'bbb', :c=>'cccc'}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "should correctly wrap arrays when converting" do
|
|
120
|
+
@m::Parser.new(:converter=>proc{|s| [:foo, s]}).call("(a,b,c)").should == [:foo, %w'a b c']
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "should correctly wrap hashes when converting" do
|
|
124
|
+
@m::Parser.new(:converter=>proc{|s| [:foo, s]}, :columns=>[:a, :b, :c]).call("(a,b,c)").should == [:foo, {:a=>'a', :b=>'b', :c=>'c'}]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "should have parser store reflection information" do
|
|
128
|
+
p = @m::Parser.new(:oid=>1, :column_oids=>[2], :columns=>[:a], :converter=>Array, :typecaster=>Hash, :column_converters=>[Array])
|
|
129
|
+
p.oid.should == 1
|
|
130
|
+
p.column_oids.should == [2]
|
|
131
|
+
p.columns.should == [:a]
|
|
132
|
+
p.converter.should == Array
|
|
133
|
+
p.typecaster.should == Hash
|
|
134
|
+
p.column_converters.should == [Array]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "should reload registered row types when reseting conversion procs" do
|
|
138
|
+
db = Sequel.mock(:host=>'postgres')
|
|
139
|
+
db.extension(:pg_row)
|
|
140
|
+
db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
141
|
+
db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
142
|
+
db.sqls
|
|
143
|
+
db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
144
|
+
db.register_row_type(:foo)
|
|
145
|
+
db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
|
|
146
|
+
"SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "should handle ArrayRows and HashRows in bound variables" do
|
|
150
|
+
@db.bound_variable_arg(1, nil).should == 1
|
|
151
|
+
@db.bound_variable_arg(@m::ArrayRow.call(["1", "abc\\'\","]), nil).should == '("1","abc\\\\\'\\",")'
|
|
152
|
+
@db.bound_variable_arg(@m::HashRow.subclass(nil, [:a, :b]).call(:a=>"1", :b=>"abc\\'\","), nil).should == '("1","abc\\\\\'\\",")'
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "should handle ArrayRows and HashRows in arrays in bound variables" do
|
|
156
|
+
@db.bound_variable_arg(1, nil).should == 1
|
|
157
|
+
@db.bound_variable_arg([@m::ArrayRow.call(["1", "abc\\'\","])], nil).should == '{"(\\"1\\",\\"abc\\\\\\\\\'\\\\\\",\\")"}'
|
|
158
|
+
@db.bound_variable_arg([@m::HashRow.subclass(nil, [:a, :b]).call(:a=>"1", :b=>"abc\\'\",")], nil).should == '{"(\\"1\\",\\"abc\\\\\\\\\'\\\\\\",\\")"}'
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "should allow registering row type parsers by introspecting system tables" do
|
|
162
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
163
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
164
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
165
|
+
@db.register_row_type(:foo)
|
|
166
|
+
@db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
|
|
167
|
+
"SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
|
|
168
|
+
p1 = @db.conversion_procs[1]
|
|
169
|
+
p1.columns.should == [:bar, :baz]
|
|
170
|
+
p1.column_oids.should == [4, 5]
|
|
171
|
+
p1.column_converters.should == [p4, p5]
|
|
172
|
+
p1.oid.should == 1
|
|
173
|
+
@db.send(:schema_column_type, 'foo').should == :pg_row_foo
|
|
174
|
+
@db.send(:schema_column_type, 'integer').should == :integer
|
|
175
|
+
|
|
176
|
+
c = p1.converter
|
|
177
|
+
c.superclass.should == @m::HashRow
|
|
178
|
+
c.columns.should == [:bar, :baz]
|
|
179
|
+
c.db_type.should == :foo
|
|
180
|
+
p1.typecaster.should == c
|
|
181
|
+
|
|
182
|
+
p1.call('(1,b)').should == {:bar=>1, :baz=>'bb'}
|
|
183
|
+
@db.typecast_value(:pg_row_foo, %w'1 b').should be_a_kind_of(@m::HashRow)
|
|
184
|
+
@db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>'1', :baz=>'b'}
|
|
185
|
+
@db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>'1', :baz=>'b'}
|
|
186
|
+
@db.literal(p1.call('(1,b)')).should == "ROW(1, 'bb')::foo"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "should allow registering row type parsers for schema qualify types" do
|
|
190
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
191
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
192
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
193
|
+
@db.register_row_type(:foo__bar)
|
|
194
|
+
@db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type INNER JOIN pg_namespace ON ((pg_namespace.oid = pg_type.typnamespace) AND (pg_namespace.nspname = 'foo')) WHERE ((typtype = 'c') AND (typname = 'bar')) LIMIT 1",
|
|
195
|
+
"SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
|
|
196
|
+
p1 = @db.conversion_procs[1]
|
|
197
|
+
p1.columns.should == [:bar, :baz]
|
|
198
|
+
p1.column_oids.should == [4, 5]
|
|
199
|
+
p1.column_converters.should == [p4, p5]
|
|
200
|
+
p1.oid.should == 1
|
|
201
|
+
|
|
202
|
+
c = p1.converter
|
|
203
|
+
c.superclass.should == @m::HashRow
|
|
204
|
+
c.columns.should == [:bar, :baz]
|
|
205
|
+
c.db_type.should == :foo__bar
|
|
206
|
+
p1.typecaster.should == c
|
|
207
|
+
|
|
208
|
+
p1.call('(1,b)').should == {:bar=>1, :baz=>'bb'}
|
|
209
|
+
@db.typecast_value(:pg_row_foo__bar, %w'1 b').should == {:bar=>'1', :baz=>'b'}
|
|
210
|
+
@db.typecast_value(:pg_row_foo__bar, :bar=>'1', :baz=>'b').should == {:bar=>'1', :baz=>'b'}
|
|
211
|
+
@db.literal(p1.call('(1,b)')).should == "ROW(1, 'bb')::foo.bar"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "should allow registering with a custom converter" do
|
|
215
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
216
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
217
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
218
|
+
c = proc{|h| [h]}
|
|
219
|
+
@db.register_row_type(:foo, :converter=>c)
|
|
220
|
+
o = @db.conversion_procs[1].call('(1,b)')
|
|
221
|
+
o.should == [{:bar=>1, :baz=>'bb'}]
|
|
222
|
+
o.first.should be_a_kind_of(Hash)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "should allow registering with a custom typecaster" do
|
|
226
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
227
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
228
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
229
|
+
@db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
|
|
230
|
+
@db.typecast_value(:pg_row_foo, %w'1 b').should be_a_kind_of(Hash)
|
|
231
|
+
@db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>1, :baz=>'bb'}
|
|
232
|
+
@db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>1, :baz=>'bb'}
|
|
233
|
+
@db.typecast_value(:pg_row_foo, 'bar'=>'1', 'baz'=>'b').should == {:bar=>0, :baz=>'aa'}
|
|
234
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
235
|
+
@db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar] || h['bar'] || 0).to_i, :baz=>(h[:baz] || h['baz'] || 'a')*2}})
|
|
236
|
+
@db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>1, :baz=>'bb'}
|
|
237
|
+
@db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>1, :baz=>'bb'}
|
|
238
|
+
@db.typecast_value(:pg_row_foo, 'bar'=>'1', 'baz'=>'b').should == {:bar=>1, :baz=>'bb'}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it "should handle conversion procs that aren't added until later" do
|
|
242
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
243
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
244
|
+
c = proc{|h| [h]}
|
|
245
|
+
@db.register_row_type(:foo, :converter=>c)
|
|
246
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
247
|
+
@db.conversion_procs[1].call('(1,b)').should == [{:bar=>1, :baz=>'bb'}]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it "should registering array type for row type if type has an array oid" do
|
|
251
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
252
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
253
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
254
|
+
@db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
|
|
255
|
+
p3 = @db.conversion_procs[3]
|
|
256
|
+
|
|
257
|
+
p3.call('{"(1,b)"}').should == [{:bar=>1, :baz=>'bb'}]
|
|
258
|
+
@db.literal(p3.call('{"(1,b)"}')).should == "ARRAY[ROW(1, 'bb')::foo]::foo[]"
|
|
259
|
+
@db.typecast_value(:foo_array, [{:bar=>'1', :baz=>'b'}]).should == [{:bar=>1, :baz=>'bb'}]
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it "should allow creating unregisted row types via Database#row_type" do
|
|
263
|
+
@db.literal(@db.row_type(:foo, [1, 2])).should == 'ROW(1, 2)::foo'
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "should allow typecasting of registered row types via Database#row_type" do
|
|
267
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
268
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
269
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
270
|
+
@db.register_row_type(:foo, :typecaster=>proc{|h| @m::HashRow.subclass(:foo, [:bar, :baz]).new({:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2})})
|
|
271
|
+
@db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
|
|
272
|
+
@db.literal(@db.row_type(:foo, {:bar=>'1', :baz=>'b'})).should == "ROW(1, 'bb')::foo"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
it "should allow parsing when typecasting registered row types via Database#row_type" do
|
|
276
|
+
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
|
277
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
|
278
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
|
|
279
|
+
@db.register_row_type(:foo, :typecaster=>proc{|h| @m::HashRow.subclass(:foo, [:bar, :baz]).new(:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2)})
|
|
280
|
+
@db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "should raise an error if attempt to use Database#row_type with an unregistered type and hash" do
|
|
284
|
+
proc{@db.literal(@db.row_type(:foo, {:bar=>'1', :baz=>'b'}))}.should raise_error(Sequel::Error)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it "should raise an error if attempt to use Database#row_type with an unhandled type" do
|
|
288
|
+
proc{@db.literal(@db.row_type(:foo, 1))}.should raise_error(Sequel::Error)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it "should return ArrayRow and HashRow values as-is" do
|
|
292
|
+
h = @m::HashRow.call(:a=>1)
|
|
293
|
+
a = @m::ArrayRow.call([1])
|
|
294
|
+
@db.row_type(:foo, h).should equal(h)
|
|
295
|
+
@db.row_type(:foo, a).should equal(a)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it "should have Sequel.pg_row return a plain ArrayRow" do
|
|
299
|
+
@db.literal(Sequel.pg_row([1, 2, 3])).should == 'ROW(1, 2, 3)'
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it "should raise an error if attempting to typecast a hash for a parser without columns" do
|
|
303
|
+
proc{@m::Parser.new.typecast(:a=>1)}.should raise_error(Sequel::Error)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "should raise an error if attempting to typecast a unhandled value for a parser" do
|
|
307
|
+
proc{@m::Parser.new.typecast(1)}.should raise_error(Sequel::Error)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it "should handle typecasting for a parser without a typecaster" do
|
|
311
|
+
@m::Parser.new.typecast([1]).should == [1]
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it "should raise an error if no columns are returned when registering a custom row type" do
|
|
315
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}]]
|
|
316
|
+
proc{@db.register_row_type(:foo)}.should raise_error(Sequel::Error)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it "should raise an error when registering a custom row type if the type is found found" do
|
|
320
|
+
@db.fetch = []
|
|
321
|
+
proc{@db.register_row_type(:foo)}.should raise_error(Sequel::Error)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
|
+
|
|
3
|
+
Sequel::Postgres::StatementCache::PGError = Sequel::Error
|
|
4
|
+
|
|
5
|
+
describe "pg_statement_cache and pg_auto_parameterize extensions" do
|
|
6
|
+
before do
|
|
7
|
+
@dbp = proc do |opts|
|
|
8
|
+
@db = Sequel.connect 'mock://postgres', :quote_identifiers=>false, :statement_cache_opts=>{:max_size=>4}.merge(opts),
|
|
9
|
+
:after_connect=>(proc do |c|
|
|
10
|
+
c.extend(Module.new do
|
|
11
|
+
def execute_query(sql, args)
|
|
12
|
+
raise Sequel::Postgres::StatementCache::PGError if @db.exec_raise
|
|
13
|
+
@db.execute(sql, :arguments=>args, :no_eq=>true)
|
|
14
|
+
end
|
|
15
|
+
def prepare(name, sql)
|
|
16
|
+
raise Sequel::Postgres::StatementCache::PGError if sql =~ /prepare_raise/
|
|
17
|
+
@ps ||= {}
|
|
18
|
+
@ps[name] = sql
|
|
19
|
+
@db._sqls << "PREPARE #{name} AS #{sql}"
|
|
20
|
+
end
|
|
21
|
+
def exec_prepared(name, args=nil)
|
|
22
|
+
@db._sqls << "EXECUTE #{name} (#{@ps[name]})#{" -- args: #{args.inspect}" if args}"
|
|
23
|
+
end
|
|
24
|
+
end)
|
|
25
|
+
end)
|
|
26
|
+
@db.extension :pg_auto_parameterize, :pg_statement_cache
|
|
27
|
+
@db.extend(Module.new do
|
|
28
|
+
attr_accessor :exec_raise
|
|
29
|
+
def _execute(c, sql, opts={})
|
|
30
|
+
opts[:no_eq] ? super : c.send(:execute_query, sql, opts[:arguments])
|
|
31
|
+
end
|
|
32
|
+
def _sqls
|
|
33
|
+
@sqls
|
|
34
|
+
end
|
|
35
|
+
def statement_cache
|
|
36
|
+
synchronize{|c| c.statement_cache}
|
|
37
|
+
end
|
|
38
|
+
end)
|
|
39
|
+
@db
|
|
40
|
+
end
|
|
41
|
+
@db = @dbp.call({})
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "should automatically prepare statements executed multiple times" do
|
|
45
|
+
@db[:table].filter(:b=>2).all
|
|
46
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
|
47
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (b = $1::int4) -- args: [2]",
|
|
48
|
+
"SELECT * FROM table WHERE (a = $1::int4) -- args: [0]",
|
|
49
|
+
"PREPARE sequel_pgap_2 AS SELECT * FROM table WHERE (a = $1::int4)",
|
|
50
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]",
|
|
51
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [2]"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "should work correctly for queries without parameters" do
|
|
55
|
+
@db[:table].filter(:b).all
|
|
56
|
+
3.times{|i| @db[:table].filter(:a).all}
|
|
57
|
+
@db.sqls.should == ["SELECT * FROM table WHERE b",
|
|
58
|
+
"SELECT * FROM table WHERE a",
|
|
59
|
+
"PREPARE sequel_pgap_2 AS SELECT * FROM table WHERE a",
|
|
60
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE a)",
|
|
61
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE a)"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should correctly return the size of the cache" do
|
|
65
|
+
sc = @db.statement_cache
|
|
66
|
+
sc.size.should == 0
|
|
67
|
+
@db[:table].filter(:b=>2).all
|
|
68
|
+
sc.size.should == 1
|
|
69
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
|
70
|
+
sc.size.should == 2
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "should correctly clear the cache" do
|
|
74
|
+
sc = @db.statement_cache
|
|
75
|
+
sc.size.should == 0
|
|
76
|
+
@db[:table].filter(:b=>2).all
|
|
77
|
+
sc.size.should == 1
|
|
78
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
|
79
|
+
sc.size.should == 2
|
|
80
|
+
sc.clear
|
|
81
|
+
sc.size.should == 0
|
|
82
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
|
83
|
+
sc.size.should == 1
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "should correctly yield each entry in the cache" do
|
|
87
|
+
@db[:table].filter(:b=>2).all
|
|
88
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
|
89
|
+
a = []
|
|
90
|
+
@db.statement_cache.each{|k, v| a << [k, v]}
|
|
91
|
+
a.sort!
|
|
92
|
+
a[0][0].should == "SELECT * FROM table WHERE (a = $1::int4)"
|
|
93
|
+
a[1][0].should == "SELECT * FROM table WHERE (b = $1::int4)"
|
|
94
|
+
s1 = a[1][1]
|
|
95
|
+
s1.cache_id.should == 1
|
|
96
|
+
s1.num_executes.should == 1
|
|
97
|
+
s1 = a[0][1]
|
|
98
|
+
s1.cache_id.should == 2
|
|
99
|
+
s1.num_executes.should == 3
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "should automatically cleanup the cache when it goes beyond its maximum size" do
|
|
103
|
+
sc = @db.statement_cache
|
|
104
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
|
105
|
+
sc.size.should == 4
|
|
106
|
+
@db[:table].filter(:b=>1).all
|
|
107
|
+
sc.size.should == 2
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "should clear statement caches when altering tables" do
|
|
111
|
+
@db[:table].filter(:b=>2).all
|
|
112
|
+
sc = @db.statement_cache
|
|
113
|
+
@db.alter_table(:foo){drop_column :bar}
|
|
114
|
+
sc.size.should == 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "should clear statement caches when dropping tables" do
|
|
118
|
+
@db[:table].filter(:b=>2).all
|
|
119
|
+
sc = @db.statement_cache
|
|
120
|
+
@db.drop_table(:foo)
|
|
121
|
+
sc.size.should == 0
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "should deallocate prepared statements when clearing the cache" do
|
|
125
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
|
126
|
+
@db.sqls
|
|
127
|
+
@db.statement_cache.clear
|
|
128
|
+
@db.sqls.should == ["DEALLOCATE sequel_pgap_1"]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "should deallocate prepared statements when cleaning up the cache" do
|
|
132
|
+
@db = @dbp.call(:sorter=>proc{|t, s| -s.num_executes})
|
|
133
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
|
134
|
+
@db[:table].filter(:a0=>1).all
|
|
135
|
+
@db.sqls
|
|
136
|
+
@db[:table].filter(:b=>1).all
|
|
137
|
+
@db.sqls.should == ["DEALLOCATE sequel_pgap_1", "SELECT * FROM table WHERE (b = $1::int4) -- args: [1]"]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "should not deallocate nonprepared statements when clearing the cache" do
|
|
141
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
|
142
|
+
@db.sqls
|
|
143
|
+
@db.statement_cache.clear
|
|
144
|
+
@db.sqls.should == []
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "should not deallocate nonprepared statements when cleaning up the cache" do
|
|
148
|
+
@db = @dbp.call(:sorter=>proc{|t, s| -s.num_executes})
|
|
149
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
|
150
|
+
@db.sqls
|
|
151
|
+
@db[:table].filter(:b=>1).all
|
|
152
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (b = $1::int4) -- args: [1]"]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "should have a configurable max_size and min_size" do
|
|
156
|
+
@db = @dbp.call(:max_size=>10, :min_size=>2)
|
|
157
|
+
10.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
|
158
|
+
sc = @db.statement_cache
|
|
159
|
+
sc.size.should == 10
|
|
160
|
+
@db[:table].filter(:b=>1).all
|
|
161
|
+
sc.size.should == 2
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "should have a configurable prepare_after" do
|
|
165
|
+
@db = @dbp.call(:prepare_after=>3)
|
|
166
|
+
4.times{|i| @db[:table].filter(:a=>i).all}
|
|
167
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (a = $1::int4) -- args: [0]",
|
|
168
|
+
"SELECT * FROM table WHERE (a = $1::int4) -- args: [1]",
|
|
169
|
+
"PREPARE sequel_pgap_1 AS SELECT * FROM table WHERE (a = $1::int4)",
|
|
170
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [2]",
|
|
171
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [3]"]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "should have a configurable sorter" do
|
|
175
|
+
@db = @dbp.call(:sorter=>proc{|t, s| s.num_executes})
|
|
176
|
+
4.times{|i| (i+1).times{@db[:table].filter(:"a#{i}"=>1).all}}
|
|
177
|
+
@db[:table].filter(:b=>1).all
|
|
178
|
+
sc = @db.statement_cache
|
|
179
|
+
a = []
|
|
180
|
+
sc.each{|k, v| a << [k, v]}
|
|
181
|
+
a.sort!
|
|
182
|
+
a[0][0].should == "SELECT * FROM table WHERE (a3 = $1::int4)"
|
|
183
|
+
a[1][0].should == "SELECT * FROM table WHERE (b = $1::int4)"
|
|
184
|
+
s1 = a[1][1]
|
|
185
|
+
s1.num_executes.should == 1
|
|
186
|
+
s1 = a[0][1]
|
|
187
|
+
s1.cache_id.should == 4
|
|
188
|
+
s1.num_executes.should == 4
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "should ignore errors when preparing queries" do
|
|
192
|
+
3.times{|i| @db[:table].filter(:prepare_raise=>1).all}
|
|
193
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]",
|
|
194
|
+
"SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]",
|
|
195
|
+
"SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]"]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it "should ignore errors when deallocating queries" do
|
|
199
|
+
3.times{|i| @db[:table].filter(:a=>1).all}
|
|
200
|
+
@db.exec_raise = true
|
|
201
|
+
@db.statement_cache.clear
|
|
202
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (a = $1::int4) -- args: [1]",
|
|
203
|
+
"PREPARE sequel_pgap_1 AS SELECT * FROM table WHERE (a = $1::int4)",
|
|
204
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]",
|
|
205
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]"]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
|
+
|
|
3
|
+
describe Sequel::Model, "PgTypecastOnLoad plugin" do
|
|
4
|
+
before do
|
|
5
|
+
@db = Sequel.mock(:host=>'postgres', :fetch=>{:id=>1, :b=>"t", :y=>"0"}, :columns=>[:id, :b, :y], :numrows=>1)
|
|
6
|
+
def @db.schema(*args)
|
|
7
|
+
[[:id, {}], [:b, {:type=>:boolean, :oid=>16}], [:y, {:type=>:integer, :oid=>20}]]
|
|
8
|
+
end
|
|
9
|
+
@c = Class.new(Sequel::Model(@db[:items]))
|
|
10
|
+
@c.plugin :pg_typecast_on_load, :b, :y
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
specify "should call the database conversion proc for all given columns" do
|
|
14
|
+
@c.first.values.should == {:id=>1, :b=>true, :y=>0}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
specify "should call the database conversion proc with value when reloading the object, for all given columns" do
|
|
18
|
+
@c.first.refresh.values.should == {:id=>1, :b=>true, :y=>0}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
specify "should call the database conversion proc with value when automatically reloading the object on creation via insert_select" do
|
|
22
|
+
@c.dataset.meta_def(:insert_select){|h| insert(h); first}
|
|
23
|
+
@c.create.values.should == {:id=>1, :b=>true, :y=>0}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
specify "should allowing setting columns separately via add_typecast_on_load_columns" do
|
|
27
|
+
@c = Class.new(Sequel::Model(@db[:items]))
|
|
28
|
+
@c.plugin :pg_typecast_on_load
|
|
29
|
+
@c.first.values.should == {:id=>1, :b=>"t", :y=>"0"}
|
|
30
|
+
@c.add_pg_typecast_on_load_columns :b
|
|
31
|
+
@c.first.values.should == {:id=>1, :b=>true, :y=>"0"}
|
|
32
|
+
@c.add_pg_typecast_on_load_columns :y
|
|
33
|
+
@c.first.values.should == {:id=>1, :b=>true, :y=>0}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
specify "should work with subclasses" do
|
|
37
|
+
@c = Class.new(Sequel::Model(@db[:items]))
|
|
38
|
+
@c.plugin :pg_typecast_on_load
|
|
39
|
+
@c.first.values.should == {:id=>1, :b=>"t", :y=>"0"}
|
|
40
|
+
|
|
41
|
+
c1 = Class.new(@c)
|
|
42
|
+
@c.add_pg_typecast_on_load_columns :b
|
|
43
|
+
@c.first.values.should == {:id=>1, :b=>true, :y=>"0"}
|
|
44
|
+
c1.first.values.should == {:id=>1, :b=>"t", :y=>"0"}
|
|
45
|
+
|
|
46
|
+
c2 = Class.new(@c)
|
|
47
|
+
@c.add_pg_typecast_on_load_columns :y
|
|
48
|
+
@c.first.values.should == {:id=>1, :b=>true, :y=>0}
|
|
49
|
+
c2.first.values.should == {:id=>1, :b=>true, :y=>"0"}
|
|
50
|
+
|
|
51
|
+
c1.add_pg_typecast_on_load_columns :y
|
|
52
|
+
c1.first.values.should == {:id=>1, :b=>"t", :y=>0}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
specify "should not mark the object as modified" do
|
|
56
|
+
@c.first.modified?.should == false
|
|
57
|
+
end
|
|
58
|
+
end
|