activerecord 2.3.18 → 3.2.22
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1014 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/performance.rb +100 -126
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +93 -99
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +247 -0
- data/lib/active_record/associations/association_scope.rb +134 -0
- data/lib/active_record/associations/belongs_to_association.rb +54 -61
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +17 -59
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +88 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +580 -0
- data/lib/active_record/associations/collection_proxy.rb +133 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +39 -119
- data/lib/active_record/associations/has_many_association.rb +60 -79
- data/lib/active_record/associations/has_many_through_association.rb +127 -206
- data/lib/active_record/associations/has_one_association.rb +55 -114
- data/lib/active_record/associations/has_one_through_association.rb +25 -26
- data/lib/active_record/associations/join_dependency/join_association.rb +159 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_helper.rb +55 -0
- data/lib/active_record/associations/preloader/association.rb +125 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/preloader.rb +181 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +693 -1337
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +111 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +136 -0
- data/lib/active_record/attribute_methods/serialization.rb +120 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
- data/lib/active_record/attribute_methods/write.rb +70 -0
- data/lib/active_record/attribute_methods.rb +211 -339
- data/lib/active_record/autosave_association.rb +179 -149
- data/lib/active_record/base.rb +401 -2907
- data/lib/active_record/callbacks.rb +91 -176
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +236 -119
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +110 -58
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +175 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -35
- data/lib/active_record/connection_adapters/abstract/quoting.rb +71 -21
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +81 -311
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +194 -78
- data/lib/active_record/connection_adapters/abstract_adapter.rb +130 -83
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
- data/lib/active_record/connection_adapters/column.rb +296 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +280 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +272 -493
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +650 -405
- data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +30 -9
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +276 -147
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +123 -0
- data/lib/active_record/dynamic_finder_match.rb +41 -14
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/dynamic_scope_match.rb +13 -15
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +86 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +695 -770
- data/lib/active_record/identity_map.rb +162 -0
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +9 -27
- data/lib/active_record/locking/optimistic.rb +76 -73
- data/lib/active_record/locking/pessimistic.rb +32 -10
- data/lib/active_record/log_subscriber.rb +72 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/migration.rb +415 -205
- data/lib/active_record/model_schema.rb +368 -0
- data/lib/active_record/nested_attributes.rb +153 -63
- data/lib/active_record/observer.rb +27 -103
- data/lib/active_record/persistence.rb +376 -0
- data/lib/active_record/query_cache.rb +49 -8
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +131 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +659 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +269 -120
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +372 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +402 -0
- data/lib/active_record/relation/predicate_builder.rb +63 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +180 -0
- data/lib/active_record/relation.rb +537 -0
- data/lib/active_record/result.rb +40 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +9 -6
- data/lib/active_record/schema_dumper.rb +55 -32
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +200 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +8 -91
- data/lib/active_record/serializers/xml_serializer.rb +43 -197
- data/lib/active_record/session_store.rb +129 -103
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +30 -23
- data/lib/active_record/timestamp.rb +95 -52
- data/lib/active_record/transactions.rb +212 -66
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/validations.rb +43 -1106
- data/lib/active_record/version.rb +5 -4
- data/lib/active_record.rb +121 -48
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +34 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +47 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +12 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- data/lib/rails/generators/active_record.rb +25 -0
- metadata +187 -363
- data/CHANGELOG +0 -5904
- data/README +0 -351
- data/RUNNING_UNIT_TESTS +0 -36
- data/Rakefile +0 -268
- data/install.rb +0 -30
- data/lib/active_record/association_preload.rb +0 -406
- data/lib/active_record/associations/association_collection.rb +0 -533
- data/lib/active_record/associations/association_proxy.rb +0 -288
- data/lib/active_record/batches.rb +0 -85
- data/lib/active_record/calculations.rb +0 -321
- data/lib/active_record/dirty.rb +0 -183
- data/lib/active_record/named_scope.rb +0 -197
- data/lib/active_record/serializers/json_serializer.rb +0 -91
- data/lib/activerecord.rb +0 -2
- data/test/assets/example.log +0 -1
- data/test/assets/flowers.jpg +0 -0
- data/test/cases/aaa_create_tables_test.rb +0 -24
- data/test/cases/active_schema_test_mysql.rb +0 -122
- data/test/cases/active_schema_test_postgresql.rb +0 -24
- data/test/cases/adapter_test.rb +0 -144
- data/test/cases/aggregations_test.rb +0 -167
- data/test/cases/ar_schema_test.rb +0 -32
- data/test/cases/associations/belongs_to_associations_test.rb +0 -438
- data/test/cases/associations/callbacks_test.rb +0 -161
- data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
- data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
- data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
- data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
- data/test/cases/associations/eager_singularization_test.rb +0 -145
- data/test/cases/associations/eager_test.rb +0 -852
- data/test/cases/associations/extension_test.rb +0 -62
- data/test/cases/associations/habtm_join_table_test.rb +0 -56
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
- data/test/cases/associations/has_many_associations_test.rb +0 -1273
- data/test/cases/associations/has_many_through_associations_test.rb +0 -360
- data/test/cases/associations/has_one_associations_test.rb +0 -330
- data/test/cases/associations/has_one_through_associations_test.rb +0 -209
- data/test/cases/associations/inner_join_association_test.rb +0 -93
- data/test/cases/associations/inverse_associations_test.rb +0 -566
- data/test/cases/associations/join_model_test.rb +0 -712
- data/test/cases/associations_test.rb +0 -282
- data/test/cases/attribute_methods_test.rb +0 -305
- data/test/cases/autosave_association_test.rb +0 -1218
- data/test/cases/base_test.rb +0 -2166
- data/test/cases/batches_test.rb +0 -81
- data/test/cases/binary_test.rb +0 -30
- data/test/cases/calculations_test.rb +0 -360
- data/test/cases/callbacks_observers_test.rb +0 -38
- data/test/cases/callbacks_test.rb +0 -438
- data/test/cases/class_inheritable_attributes_test.rb +0 -32
- data/test/cases/column_alias_test.rb +0 -17
- data/test/cases/column_definition_test.rb +0 -70
- data/test/cases/connection_pool_test.rb +0 -25
- data/test/cases/connection_test_firebird.rb +0 -8
- data/test/cases/connection_test_mysql.rb +0 -65
- data/test/cases/copy_table_test_sqlite.rb +0 -80
- data/test/cases/counter_cache_test.rb +0 -84
- data/test/cases/database_statements_test.rb +0 -12
- data/test/cases/datatype_test_postgresql.rb +0 -204
- data/test/cases/date_time_test.rb +0 -37
- data/test/cases/default_test_firebird.rb +0 -16
- data/test/cases/defaults_test.rb +0 -111
- data/test/cases/deprecated_finder_test.rb +0 -30
- data/test/cases/dirty_test.rb +0 -316
- data/test/cases/finder_respond_to_test.rb +0 -76
- data/test/cases/finder_test.rb +0 -1098
- data/test/cases/fixtures_test.rb +0 -661
- data/test/cases/helper.rb +0 -68
- data/test/cases/i18n_test.rb +0 -46
- data/test/cases/inheritance_test.rb +0 -262
- data/test/cases/invalid_date_test.rb +0 -24
- data/test/cases/json_serialization_test.rb +0 -219
- data/test/cases/lifecycle_test.rb +0 -193
- data/test/cases/locking_test.rb +0 -350
- data/test/cases/method_scoping_test.rb +0 -704
- data/test/cases/migration_test.rb +0 -1649
- data/test/cases/migration_test_firebird.rb +0 -124
- data/test/cases/mixin_test.rb +0 -96
- data/test/cases/modules_test.rb +0 -109
- data/test/cases/multiple_db_test.rb +0 -85
- data/test/cases/named_scope_test.rb +0 -372
- data/test/cases/nested_attributes_test.rb +0 -840
- data/test/cases/pk_test.rb +0 -119
- data/test/cases/pooled_connections_test.rb +0 -103
- data/test/cases/query_cache_test.rb +0 -129
- data/test/cases/readonly_test.rb +0 -107
- data/test/cases/reflection_test.rb +0 -234
- data/test/cases/reload_models_test.rb +0 -22
- data/test/cases/repair_helper.rb +0 -50
- data/test/cases/reserved_word_test_mysql.rb +0 -176
- data/test/cases/sanitize_test.rb +0 -25
- data/test/cases/schema_authorization_test_postgresql.rb +0 -75
- data/test/cases/schema_dumper_test.rb +0 -211
- data/test/cases/schema_test_postgresql.rb +0 -178
- data/test/cases/serialization_test.rb +0 -47
- data/test/cases/sp_test_mysql.rb +0 -16
- data/test/cases/synonym_test_oracle.rb +0 -17
- data/test/cases/timestamp_test.rb +0 -75
- data/test/cases/transactions_test.rb +0 -543
- data/test/cases/unconnected_test.rb +0 -32
- data/test/cases/validations_i18n_test.rb +0 -925
- data/test/cases/validations_test.rb +0 -1684
- data/test/cases/xml_serialization_test.rb +0 -240
- data/test/cases/yaml_serialization_test.rb +0 -11
- data/test/config.rb +0 -5
- data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
- data/test/connections/jdbc_jdbch2/connection.rb +0 -18
- data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
- data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
- data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
- data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
- data/test/connections/native_db2/connection.rb +0 -25
- data/test/connections/native_firebird/connection.rb +0 -26
- data/test/connections/native_frontbase/connection.rb +0 -27
- data/test/connections/native_mysql/connection.rb +0 -25
- data/test/connections/native_openbase/connection.rb +0 -21
- data/test/connections/native_oracle/connection.rb +0 -27
- data/test/connections/native_postgresql/connection.rb +0 -21
- data/test/connections/native_sqlite/connection.rb +0 -25
- data/test/connections/native_sqlite3/connection.rb +0 -25
- data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
- data/test/connections/native_sybase/connection.rb +0 -23
- data/test/fixtures/accounts.yml +0 -29
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author_addresses.yml +0 -5
- data/test/fixtures/author_favorites.yml +0 -4
- data/test/fixtures/authors.yml +0 -9
- data/test/fixtures/binaries.yml +0 -132
- data/test/fixtures/books.yml +0 -7
- data/test/fixtures/categories/special_categories.yml +0 -9
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
- data/test/fixtures/categories.yml +0 -14
- data/test/fixtures/categories_ordered.yml +0 -7
- data/test/fixtures/categories_posts.yml +0 -23
- data/test/fixtures/categorizations.yml +0 -17
- data/test/fixtures/clubs.yml +0 -6
- data/test/fixtures/comments.yml +0 -59
- data/test/fixtures/companies.yml +0 -56
- data/test/fixtures/computers.yml +0 -4
- data/test/fixtures/courses.yml +0 -7
- data/test/fixtures/customers.yml +0 -26
- data/test/fixtures/developers.yml +0 -21
- data/test/fixtures/developers_projects.yml +0 -17
- data/test/fixtures/edges.yml +0 -6
- data/test/fixtures/entrants.yml +0 -14
- data/test/fixtures/faces.yml +0 -11
- data/test/fixtures/fk_test_has_fk.yml +0 -3
- data/test/fixtures/fk_test_has_pk.yml +0 -2
- data/test/fixtures/funny_jokes.yml +0 -10
- data/test/fixtures/interests.yml +0 -33
- data/test/fixtures/items.yml +0 -4
- data/test/fixtures/jobs.yml +0 -7
- data/test/fixtures/legacy_things.yml +0 -3
- data/test/fixtures/mateys.yml +0 -4
- data/test/fixtures/member_types.yml +0 -6
- data/test/fixtures/members.yml +0 -6
- data/test/fixtures/memberships.yml +0 -20
- data/test/fixtures/men.yml +0 -5
- data/test/fixtures/minimalistics.yml +0 -2
- data/test/fixtures/mixed_case_monkeys.yml +0 -6
- data/test/fixtures/mixins.yml +0 -29
- data/test/fixtures/movies.yml +0 -7
- data/test/fixtures/naked/csv/accounts.csv +0 -1
- data/test/fixtures/naked/yml/accounts.yml +0 -1
- data/test/fixtures/naked/yml/companies.yml +0 -1
- data/test/fixtures/naked/yml/courses.yml +0 -1
- data/test/fixtures/organizations.yml +0 -5
- data/test/fixtures/owners.yml +0 -7
- data/test/fixtures/parrots.yml +0 -27
- data/test/fixtures/parrots_pirates.yml +0 -7
- data/test/fixtures/people.yml +0 -15
- data/test/fixtures/pets.yml +0 -14
- data/test/fixtures/pirates.yml +0 -9
- data/test/fixtures/polymorphic_designs.yml +0 -19
- data/test/fixtures/polymorphic_prices.yml +0 -19
- data/test/fixtures/posts.yml +0 -52
- data/test/fixtures/price_estimates.yml +0 -7
- data/test/fixtures/projects.yml +0 -7
- data/test/fixtures/readers.yml +0 -9
- data/test/fixtures/references.yml +0 -17
- data/test/fixtures/reserved_words/distinct.yml +0 -5
- data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
- data/test/fixtures/reserved_words/group.yml +0 -14
- data/test/fixtures/reserved_words/select.yml +0 -8
- data/test/fixtures/reserved_words/values.yml +0 -7
- data/test/fixtures/ships.yml +0 -5
- data/test/fixtures/sponsors.yml +0 -9
- data/test/fixtures/subscribers.yml +0 -7
- data/test/fixtures/subscriptions.yml +0 -12
- data/test/fixtures/taggings.yml +0 -28
- data/test/fixtures/tags.yml +0 -7
- data/test/fixtures/tasks.yml +0 -7
- data/test/fixtures/tees.yml +0 -4
- data/test/fixtures/ties.yml +0 -4
- data/test/fixtures/topics.yml +0 -42
- data/test/fixtures/toys.yml +0 -4
- data/test/fixtures/treasures.yml +0 -10
- data/test/fixtures/vertices.yml +0 -4
- data/test/fixtures/warehouse-things.yml +0 -3
- data/test/fixtures/zines.yml +0 -5
- data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
- data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
- data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
- data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
- data/test/migrations/duplicate/3_foo.rb +0 -7
- data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
- data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
- data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
- data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
- data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
- data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
- data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
- data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
- data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
- data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
- data/test/migrations/missing/1_people_have_last_names.rb +0 -9
- data/test/migrations/missing/3_we_need_reminders.rb +0 -12
- data/test/migrations/missing/4_innocent_jointable.rb +0 -12
- data/test/migrations/valid/1_people_have_last_names.rb +0 -9
- data/test/migrations/valid/2_we_need_reminders.rb +0 -12
- data/test/migrations/valid/3_innocent_jointable.rb +0 -12
- data/test/models/author.rb +0 -151
- data/test/models/auto_id.rb +0 -4
- data/test/models/binary.rb +0 -2
- data/test/models/bird.rb +0 -9
- data/test/models/book.rb +0 -4
- data/test/models/categorization.rb +0 -5
- data/test/models/category.rb +0 -34
- data/test/models/citation.rb +0 -6
- data/test/models/club.rb +0 -13
- data/test/models/column_name.rb +0 -3
- data/test/models/comment.rb +0 -29
- data/test/models/company.rb +0 -173
- data/test/models/company_in_module.rb +0 -78
- data/test/models/computer.rb +0 -3
- data/test/models/contact.rb +0 -16
- data/test/models/contract.rb +0 -5
- data/test/models/course.rb +0 -3
- data/test/models/customer.rb +0 -73
- data/test/models/default.rb +0 -2
- data/test/models/developer.rb +0 -101
- data/test/models/edge.rb +0 -5
- data/test/models/entrant.rb +0 -3
- data/test/models/essay.rb +0 -3
- data/test/models/event.rb +0 -3
- data/test/models/event_author.rb +0 -8
- data/test/models/face.rb +0 -7
- data/test/models/guid.rb +0 -2
- data/test/models/interest.rb +0 -5
- data/test/models/invoice.rb +0 -4
- data/test/models/item.rb +0 -7
- data/test/models/job.rb +0 -5
- data/test/models/joke.rb +0 -3
- data/test/models/keyboard.rb +0 -3
- data/test/models/legacy_thing.rb +0 -3
- data/test/models/line_item.rb +0 -3
- data/test/models/man.rb +0 -9
- data/test/models/matey.rb +0 -4
- data/test/models/member.rb +0 -12
- data/test/models/member_detail.rb +0 -5
- data/test/models/member_type.rb +0 -3
- data/test/models/membership.rb +0 -9
- data/test/models/minimalistic.rb +0 -2
- data/test/models/mixed_case_monkey.rb +0 -3
- data/test/models/movie.rb +0 -5
- data/test/models/order.rb +0 -4
- data/test/models/organization.rb +0 -6
- data/test/models/owner.rb +0 -5
- data/test/models/parrot.rb +0 -22
- data/test/models/person.rb +0 -16
- data/test/models/pet.rb +0 -5
- data/test/models/pirate.rb +0 -80
- data/test/models/polymorphic_design.rb +0 -3
- data/test/models/polymorphic_price.rb +0 -3
- data/test/models/post.rb +0 -102
- data/test/models/price_estimate.rb +0 -3
- data/test/models/project.rb +0 -30
- data/test/models/reader.rb +0 -4
- data/test/models/reference.rb +0 -4
- data/test/models/reply.rb +0 -46
- data/test/models/ship.rb +0 -19
- data/test/models/ship_part.rb +0 -7
- data/test/models/sponsor.rb +0 -4
- data/test/models/subject.rb +0 -4
- data/test/models/subscriber.rb +0 -8
- data/test/models/subscription.rb +0 -4
- data/test/models/tag.rb +0 -7
- data/test/models/tagging.rb +0 -10
- data/test/models/task.rb +0 -3
- data/test/models/tee.rb +0 -4
- data/test/models/tie.rb +0 -4
- data/test/models/topic.rb +0 -80
- data/test/models/toy.rb +0 -6
- data/test/models/treasure.rb +0 -8
- data/test/models/vertex.rb +0 -9
- data/test/models/warehouse_thing.rb +0 -5
- data/test/models/zine.rb +0 -3
- data/test/schema/mysql_specific_schema.rb +0 -31
- data/test/schema/postgresql_specific_schema.rb +0 -114
- data/test/schema/schema.rb +0 -550
- data/test/schema/schema2.rb +0 -6
- data/test/schema/sqlite_specific_schema.rb +0 -25
@@ -1,20 +1,11 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'active_record/connection_adapters/statement_pool'
|
4
|
+
require 'arel/visitors/bind_visitor'
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
begin
|
7
|
-
require_library_or_gem 'postgres'
|
8
|
-
class PGresult
|
9
|
-
alias_method :nfields, :num_fields unless self.method_defined?(:nfields)
|
10
|
-
alias_method :ntuples, :num_tuples unless self.method_defined?(:ntuples)
|
11
|
-
alias_method :ftype, :type unless self.method_defined?(:ftype)
|
12
|
-
alias_method :cmd_tuples, :cmdtuples unless self.method_defined?(:cmd_tuples)
|
13
|
-
end
|
14
|
-
rescue LoadError
|
15
|
-
raise e
|
16
|
-
end
|
17
|
-
end
|
6
|
+
# Make sure we're using pg high enough for PGResult#values
|
7
|
+
gem 'pg', '~> 0.11'
|
8
|
+
require 'pg'
|
18
9
|
|
19
10
|
module ActiveRecord
|
20
11
|
class Base
|
@@ -26,7 +17,7 @@ module ActiveRecord
|
|
26
17
|
username = config[:username].to_s if config[:username]
|
27
18
|
password = config[:password].to_s if config[:password]
|
28
19
|
|
29
|
-
if config.
|
20
|
+
if config.key?(:database)
|
30
21
|
database = config[:database]
|
31
22
|
else
|
32
23
|
raise ArgumentError, "No database specified. Missing argument: database."
|
@@ -39,12 +30,6 @@ module ActiveRecord
|
|
39
30
|
end
|
40
31
|
|
41
32
|
module ConnectionAdapters
|
42
|
-
class TableDefinition
|
43
|
-
def xml(*args)
|
44
|
-
options = args.extract_options!
|
45
|
-
column(args[0], 'xml', options)
|
46
|
-
end
|
47
|
-
end
|
48
33
|
# PostgreSQL-specific extensions to column definitions in a table.
|
49
34
|
class PostgreSQLColumn < Column #:nodoc:
|
50
35
|
# Instantiates a new PostgreSQL column definition in a table.
|
@@ -52,6 +37,22 @@ module ActiveRecord
|
|
52
37
|
super(name, self.class.extract_value_from_default(default), sql_type, null)
|
53
38
|
end
|
54
39
|
|
40
|
+
# :stopdoc:
|
41
|
+
class << self
|
42
|
+
attr_accessor :money_precision
|
43
|
+
def string_to_time(string)
|
44
|
+
return string unless String === string
|
45
|
+
|
46
|
+
case string
|
47
|
+
when 'infinity' then 1.0 / 0.0
|
48
|
+
when '-infinity' then -1.0 / 0.0
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# :startdoc:
|
55
|
+
|
55
56
|
private
|
56
57
|
def extract_limit(sql_type)
|
57
58
|
case sql_type
|
@@ -69,9 +70,11 @@ module ActiveRecord
|
|
69
70
|
|
70
71
|
# Extracts the precision from PostgreSQL-specific data types.
|
71
72
|
def extract_precision(sql_type)
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
if sql_type == 'money'
|
74
|
+
self.class.money_precision
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
75
78
|
end
|
76
79
|
|
77
80
|
# Maps PostgreSQL-specific data types to logical Rails types.
|
@@ -81,18 +84,18 @@ module ActiveRecord
|
|
81
84
|
when /^(?:real|double precision)$/
|
82
85
|
:float
|
83
86
|
# Monetary types
|
84
|
-
when
|
87
|
+
when 'money'
|
85
88
|
:decimal
|
86
89
|
# Character types
|
87
90
|
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
88
91
|
:string
|
89
92
|
# Binary data types
|
90
|
-
when
|
93
|
+
when 'bytea'
|
91
94
|
:binary
|
92
95
|
# Date/time types
|
93
96
|
when /^timestamp with(?:out)? time zone$/
|
94
97
|
:datetime
|
95
|
-
when
|
98
|
+
when 'interval'
|
96
99
|
:string
|
97
100
|
# Geometric types
|
98
101
|
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
@@ -104,13 +107,22 @@ module ActiveRecord
|
|
104
107
|
when /^bit(?: varying)?(?:\(\d+\))?$/
|
105
108
|
:string
|
106
109
|
# XML type
|
107
|
-
when
|
110
|
+
when 'xml'
|
108
111
|
:xml
|
112
|
+
# tsvector type
|
113
|
+
when 'tsvector'
|
114
|
+
:tsvector
|
109
115
|
# Arrays
|
110
116
|
when /^\D+\[\]$/
|
111
117
|
:string
|
112
118
|
# Object identifier types
|
113
|
-
when
|
119
|
+
when 'oid'
|
120
|
+
:integer
|
121
|
+
# UUID type
|
122
|
+
when 'uuid'
|
123
|
+
:string
|
124
|
+
# Small and big integer types
|
125
|
+
when /^(?:small|big)int$/
|
114
126
|
:integer
|
115
127
|
# Pass through all types that are not specific to PostgreSQL.
|
116
128
|
else
|
@@ -121,15 +133,20 @@ module ActiveRecord
|
|
121
133
|
# Extracts the value from a PostgreSQL column default definition.
|
122
134
|
def self.extract_value_from_default(default)
|
123
135
|
case default
|
136
|
+
# This is a performance optimization for Ruby 1.9.2 in development.
|
137
|
+
# If the value is nil, we return nil straight away without checking
|
138
|
+
# the regular expressions. If we check each regular expression,
|
139
|
+
# Regexp#=== will call NilClass#to_str, which will trigger
|
140
|
+
# method_missing (defined by whiny nil in ActiveSupport) which
|
141
|
+
# makes this method very very slow.
|
142
|
+
when NilClass
|
143
|
+
nil
|
124
144
|
# Numeric types
|
125
|
-
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
|
145
|
+
when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
|
126
146
|
$1
|
127
147
|
# Character types
|
128
|
-
when /\A'(.*)'
|
148
|
+
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
|
129
149
|
$1
|
130
|
-
# Character types (8.1 formatting)
|
131
|
-
when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
|
132
|
-
$1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
|
133
150
|
# Binary data types
|
134
151
|
when /\A'(.*)'::bytea\z/m
|
135
152
|
$1
|
@@ -168,9 +185,7 @@ module ActiveRecord
|
|
168
185
|
end
|
169
186
|
end
|
170
187
|
end
|
171
|
-
end
|
172
188
|
|
173
|
-
module ConnectionAdapters
|
174
189
|
# The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
|
175
190
|
# Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
|
176
191
|
#
|
@@ -181,15 +196,29 @@ module ActiveRecord
|
|
181
196
|
# * <tt>:username</tt> - Defaults to nothing.
|
182
197
|
# * <tt>:password</tt> - Defaults to nothing.
|
183
198
|
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
184
|
-
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
185
|
-
#
|
186
|
-
# * <tt>:
|
187
|
-
#
|
199
|
+
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
200
|
+
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
201
|
+
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
202
|
+
# <encoding></tt> call on the connection.
|
203
|
+
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
204
|
+
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
188
205
|
class PostgreSQLAdapter < AbstractAdapter
|
189
|
-
|
206
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
207
|
+
def xml(*args)
|
208
|
+
options = args.extract_options!
|
209
|
+
column(args[0], 'xml', options)
|
210
|
+
end
|
211
|
+
|
212
|
+
def tsvector(*args)
|
213
|
+
options = args.extract_options!
|
214
|
+
column(args[0], 'tsvector', options)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
ADAPTER_NAME = 'PostgreSQL'
|
190
219
|
|
191
220
|
NATIVE_DATABASE_TYPES = {
|
192
|
-
:primary_key => "serial primary key"
|
221
|
+
:primary_key => "serial primary key",
|
193
222
|
:string => { :name => "character varying", :limit => 255 },
|
194
223
|
:text => { :name => "text" },
|
195
224
|
:integer => { :name => "integer" },
|
@@ -201,7 +230,8 @@ module ActiveRecord
|
|
201
230
|
:date => { :name => "date" },
|
202
231
|
:binary => { :name => "bytea" },
|
203
232
|
:boolean => { :name => "boolean" },
|
204
|
-
:xml => { :name => "xml" }
|
233
|
+
:xml => { :name => "xml" },
|
234
|
+
:tsvector => { :name => "tsvector" }
|
205
235
|
}
|
206
236
|
|
207
237
|
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
@@ -209,41 +239,129 @@ module ActiveRecord
|
|
209
239
|
ADAPTER_NAME
|
210
240
|
end
|
211
241
|
|
242
|
+
# Returns +true+, since this connection adapter supports prepared statement
|
243
|
+
# caching.
|
244
|
+
def supports_statement_cache?
|
245
|
+
true
|
246
|
+
end
|
247
|
+
|
248
|
+
def supports_index_sort_order?
|
249
|
+
true
|
250
|
+
end
|
251
|
+
|
252
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
253
|
+
def initialize(connection, max)
|
254
|
+
super
|
255
|
+
@counter = 0
|
256
|
+
@cache = Hash.new { |h,pid| h[pid] = {} }
|
257
|
+
end
|
258
|
+
|
259
|
+
def each(&block); cache.each(&block); end
|
260
|
+
def key?(key); cache.key?(key); end
|
261
|
+
def [](key); cache[key]; end
|
262
|
+
def length; cache.length; end
|
263
|
+
|
264
|
+
def next_key
|
265
|
+
"a#{@counter + 1}"
|
266
|
+
end
|
267
|
+
|
268
|
+
def []=(sql, key)
|
269
|
+
while @max <= cache.size
|
270
|
+
dealloc(cache.shift.last)
|
271
|
+
end
|
272
|
+
@counter += 1
|
273
|
+
cache[sql] = key
|
274
|
+
end
|
275
|
+
|
276
|
+
def clear
|
277
|
+
cache.each_value do |stmt_key|
|
278
|
+
dealloc stmt_key
|
279
|
+
end
|
280
|
+
cache.clear
|
281
|
+
end
|
282
|
+
|
283
|
+
def delete(sql_key)
|
284
|
+
dealloc cache[sql_key]
|
285
|
+
cache.delete sql_key
|
286
|
+
end
|
287
|
+
|
288
|
+
private
|
289
|
+
def cache
|
290
|
+
@cache[$$]
|
291
|
+
end
|
292
|
+
|
293
|
+
def dealloc(key)
|
294
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
295
|
+
end
|
296
|
+
|
297
|
+
def connection_active?
|
298
|
+
@connection.status == PGconn::CONNECTION_OK
|
299
|
+
rescue PGError
|
300
|
+
false
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
|
305
|
+
include Arel::Visitors::BindVisitor
|
306
|
+
end
|
307
|
+
|
212
308
|
# Initializes and connects a PostgreSQL adapter.
|
213
309
|
def initialize(connection, logger, connection_parameters, config)
|
214
310
|
super(connection, logger)
|
311
|
+
|
312
|
+
if config.fetch(:prepared_statements) { true }
|
313
|
+
@visitor = Arel::Visitors::PostgreSQL.new self
|
314
|
+
else
|
315
|
+
@visitor = BindSubstitution.new self
|
316
|
+
end
|
317
|
+
|
215
318
|
@connection_parameters, @config = connection_parameters, config
|
216
319
|
|
320
|
+
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
321
|
+
@local_tz = nil
|
322
|
+
@table_alias_length = nil
|
323
|
+
|
217
324
|
connect
|
325
|
+
@statements = StatementPool.new @connection,
|
326
|
+
config.fetch(:statement_limit) { 1000 }
|
327
|
+
|
328
|
+
if postgresql_version < 80200
|
329
|
+
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
|
330
|
+
end
|
331
|
+
|
332
|
+
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
333
|
+
end
|
334
|
+
|
335
|
+
# Clears the prepared statements cache.
|
336
|
+
def clear_cache!
|
337
|
+
@statements.clear
|
218
338
|
end
|
219
339
|
|
220
340
|
# Is this connection alive and ready for queries?
|
221
341
|
def active?
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
# We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
|
226
|
-
@connection.query 'SELECT 1'
|
227
|
-
true
|
228
|
-
end
|
229
|
-
# postgres-pr raises a NoMethodError when querying if no connection is available.
|
230
|
-
rescue PGError, NoMethodError
|
342
|
+
@connection.query 'SELECT 1'
|
343
|
+
true
|
344
|
+
rescue PGError
|
231
345
|
false
|
232
346
|
end
|
233
347
|
|
234
348
|
# Close then reopen the connection.
|
235
349
|
def reconnect!
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
disconnect!
|
241
|
-
connect
|
242
|
-
end
|
350
|
+
clear_cache!
|
351
|
+
@connection.reset
|
352
|
+
@open_transactions = 0
|
353
|
+
configure_connection
|
243
354
|
end
|
244
355
|
|
245
|
-
|
356
|
+
def reset!
|
357
|
+
clear_cache!
|
358
|
+
super
|
359
|
+
end
|
360
|
+
|
361
|
+
# Disconnects from the database if already connected. Otherwise, this
|
362
|
+
# method does nothing.
|
246
363
|
def disconnect!
|
364
|
+
clear_cache!
|
247
365
|
@connection.close rescue nil
|
248
366
|
end
|
249
367
|
|
@@ -251,12 +369,12 @@ module ActiveRecord
|
|
251
369
|
NATIVE_DATABASE_TYPES
|
252
370
|
end
|
253
371
|
|
254
|
-
#
|
372
|
+
# Returns true, since this connection adapter supports migrations.
|
255
373
|
def supports_migrations?
|
256
374
|
true
|
257
375
|
end
|
258
376
|
|
259
|
-
# Does PostgreSQL support finding primary key on non-
|
377
|
+
# Does PostgreSQL support finding primary key on non-Active Record tables?
|
260
378
|
def supports_primary_key? #:nodoc:
|
261
379
|
true
|
262
380
|
end
|
@@ -264,126 +382,92 @@ module ActiveRecord
|
|
264
382
|
# Enable standard-conforming strings if available.
|
265
383
|
def set_standard_conforming_strings
|
266
384
|
old, self.client_min_messages = client_min_messages, 'panic'
|
267
|
-
execute('SET standard_conforming_strings = on') rescue nil
|
385
|
+
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
268
386
|
ensure
|
269
387
|
self.client_min_messages = old
|
270
388
|
end
|
271
389
|
|
272
390
|
def supports_insert_with_returning?
|
273
|
-
|
391
|
+
true
|
274
392
|
end
|
275
393
|
|
276
394
|
def supports_ddl_transactions?
|
277
395
|
true
|
278
396
|
end
|
279
397
|
|
398
|
+
# Returns true, since this connection adapter supports savepoints.
|
280
399
|
def supports_savepoints?
|
281
400
|
true
|
282
401
|
end
|
283
402
|
|
284
|
-
# Returns
|
285
|
-
|
403
|
+
# Returns true.
|
404
|
+
def supports_explain?
|
405
|
+
true
|
406
|
+
end
|
407
|
+
|
408
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
286
409
|
def table_alias_length
|
287
|
-
@table_alias_length ||=
|
410
|
+
@table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
|
288
411
|
end
|
289
412
|
|
290
413
|
# QUOTING ==================================================
|
291
414
|
|
292
415
|
# Escapes binary strings for bytea input to the database.
|
293
|
-
def escape_bytea(
|
294
|
-
|
295
|
-
self.class.instance_eval do
|
296
|
-
define_method(:escape_bytea) do |value|
|
297
|
-
@connection.escape_bytea(value) if value
|
298
|
-
end
|
299
|
-
end
|
300
|
-
elsif PGconn.respond_to?(:escape_bytea)
|
301
|
-
self.class.instance_eval do
|
302
|
-
define_method(:escape_bytea) do |value|
|
303
|
-
PGconn.escape_bytea(value) if value
|
304
|
-
end
|
305
|
-
end
|
306
|
-
else
|
307
|
-
self.class.instance_eval do
|
308
|
-
define_method(:escape_bytea) do |value|
|
309
|
-
if value
|
310
|
-
result = ''
|
311
|
-
value.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
312
|
-
result
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
end
|
317
|
-
escape_bytea(original_value)
|
416
|
+
def escape_bytea(value)
|
417
|
+
@connection.escape_bytea(value) if value
|
318
418
|
end
|
319
419
|
|
320
420
|
# Unescapes bytea output from a database to the binary string it represents.
|
321
421
|
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
322
422
|
# on escaped binary output from database drive.
|
323
|
-
def unescape_bytea(
|
324
|
-
|
325
|
-
# or an unescaped Active Record attribute that was just written.
|
326
|
-
if @connection.respond_to?(:unescape_bytea)
|
327
|
-
self.class.instance_eval do
|
328
|
-
define_method(:unescape_bytea) do |value|
|
329
|
-
@connection.unescape_bytea(value) if value
|
330
|
-
end
|
331
|
-
end
|
332
|
-
elsif PGconn.respond_to?(:unescape_bytea)
|
333
|
-
self.class.instance_eval do
|
334
|
-
define_method(:unescape_bytea) do |value|
|
335
|
-
PGconn.unescape_bytea(value) if value
|
336
|
-
end
|
337
|
-
end
|
338
|
-
else
|
339
|
-
raise 'Your PostgreSQL connection does not support unescape_bytea. Try upgrading to pg 0.9.0 or later.'
|
340
|
-
end
|
341
|
-
unescape_bytea(original_value)
|
423
|
+
def unescape_bytea(value)
|
424
|
+
@connection.unescape_bytea(value) if value
|
342
425
|
end
|
343
426
|
|
344
427
|
# Quotes PostgreSQL-specific data types for SQL input.
|
345
428
|
def quote(value, column = nil) #:nodoc:
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
429
|
+
return super unless column
|
430
|
+
|
431
|
+
case value
|
432
|
+
when Float
|
433
|
+
return super unless value.infinite? && column.type == :datetime
|
434
|
+
"'#{value.to_s.downcase}'"
|
435
|
+
when Numeric
|
436
|
+
return super unless column.sql_type == 'money'
|
351
437
|
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
352
|
-
"'#{value
|
353
|
-
|
354
|
-
case
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
438
|
+
"'#{value}'"
|
439
|
+
when String
|
440
|
+
case column.sql_type
|
441
|
+
when 'bytea' then "'#{escape_bytea(value)}'"
|
442
|
+
when 'xml' then "xml '#{quote_string(value)}'"
|
443
|
+
when /^bit/
|
444
|
+
case value
|
445
|
+
when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
|
446
|
+
when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
|
447
|
+
end
|
448
|
+
else
|
449
|
+
super
|
359
450
|
end
|
360
451
|
else
|
361
452
|
super
|
362
453
|
end
|
363
454
|
end
|
364
455
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
end
|
373
|
-
elsif PGconn.respond_to?(:escape)
|
374
|
-
self.class.instance_eval do
|
375
|
-
define_method(:quote_string) do |s|
|
376
|
-
PGconn.escape(s)
|
377
|
-
end
|
378
|
-
end
|
456
|
+
def type_cast(value, column)
|
457
|
+
return super unless column
|
458
|
+
|
459
|
+
case value
|
460
|
+
when String
|
461
|
+
return super unless 'bytea' == column.sql_type
|
462
|
+
{ :value => value, :format => 1 }
|
379
463
|
else
|
380
|
-
|
381
|
-
# that don't define PGconn.escape.
|
382
|
-
self.class.instance_eval do
|
383
|
-
remove_method(:quote_string)
|
384
|
-
end
|
464
|
+
super
|
385
465
|
end
|
386
|
-
|
466
|
+
end
|
467
|
+
|
468
|
+
# Quotes strings for use in SQL input.
|
469
|
+
def quote_string(s) #:nodoc:
|
470
|
+
@connection.escape(s)
|
387
471
|
end
|
388
472
|
|
389
473
|
# Checks the following cases:
|
@@ -420,28 +504,73 @@ module ActiveRecord
|
|
420
504
|
end
|
421
505
|
end
|
422
506
|
|
507
|
+
# Set the authorized user for this session
|
508
|
+
def session_auth=(user)
|
509
|
+
clear_cache!
|
510
|
+
exec_query "SET SESSION AUTHORIZATION #{user}"
|
511
|
+
end
|
512
|
+
|
423
513
|
# REFERENTIAL INTEGRITY ====================================
|
424
514
|
|
425
|
-
def supports_disable_referential_integrity?
|
426
|
-
|
427
|
-
(version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
|
428
|
-
rescue
|
429
|
-
return false
|
515
|
+
def supports_disable_referential_integrity? #:nodoc:
|
516
|
+
true
|
430
517
|
end
|
431
518
|
|
432
|
-
def disable_referential_integrity
|
433
|
-
if supports_disable_referential_integrity?
|
519
|
+
def disable_referential_integrity #:nodoc:
|
520
|
+
if supports_disable_referential_integrity? then
|
434
521
|
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
435
522
|
end
|
436
523
|
yield
|
437
524
|
ensure
|
438
|
-
if supports_disable_referential_integrity?
|
525
|
+
if supports_disable_referential_integrity? then
|
439
526
|
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
440
527
|
end
|
441
528
|
end
|
442
529
|
|
443
530
|
# DATABASE STATEMENTS ======================================
|
444
531
|
|
532
|
+
def explain(arel, binds = [])
|
533
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
534
|
+
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
535
|
+
end
|
536
|
+
|
537
|
+
class ExplainPrettyPrinter # :nodoc:
|
538
|
+
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
539
|
+
# PostgreSQL shell:
|
540
|
+
#
|
541
|
+
# QUERY PLAN
|
542
|
+
# ------------------------------------------------------------------------------
|
543
|
+
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
544
|
+
# Join Filter: (posts.user_id = users.id)
|
545
|
+
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
546
|
+
# Index Cond: (id = 1)
|
547
|
+
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
548
|
+
# Filter: (posts.user_id = 1)
|
549
|
+
# (6 rows)
|
550
|
+
#
|
551
|
+
def pp(result)
|
552
|
+
header = result.columns.first
|
553
|
+
lines = result.rows.map(&:first)
|
554
|
+
|
555
|
+
# We add 2 because there's one char of padding at both sides, note
|
556
|
+
# the extra hyphens in the example above.
|
557
|
+
width = [header, *lines].map(&:length).max + 2
|
558
|
+
|
559
|
+
pp = []
|
560
|
+
|
561
|
+
pp << header.center(width).rstrip
|
562
|
+
pp << '-' * width
|
563
|
+
|
564
|
+
pp += lines.map {|line| " #{line}"}
|
565
|
+
|
566
|
+
nrows = result.rows.length
|
567
|
+
rows_label = nrows == 1 ? 'row' : 'rows'
|
568
|
+
pp << "(#{nrows} #{rows_label})"
|
569
|
+
|
570
|
+
pp.join("\n") + "\n"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
445
574
|
# Executes a SELECT query and returns an array of rows. Each row is an
|
446
575
|
# array of field values.
|
447
576
|
def select_rows(sql, name = nil)
|
@@ -449,68 +578,68 @@ module ActiveRecord
|
|
449
578
|
end
|
450
579
|
|
451
580
|
# Executes an INSERT query and returns the new record's ID
|
452
|
-
def
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
if supports_insert_with_returning?
|
458
|
-
pk, sequence_name = *pk_and_sequence_for(table) unless pk
|
459
|
-
if pk
|
460
|
-
id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
461
|
-
clear_query_cache
|
462
|
-
return id
|
463
|
-
end
|
581
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
582
|
+
unless pk
|
583
|
+
# Extract the table from the insert sql. Yuck.
|
584
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
585
|
+
pk = primary_key(table_ref) if table_ref
|
464
586
|
end
|
465
587
|
|
466
|
-
|
467
|
-
|
468
|
-
insert_id
|
588
|
+
if pk
|
589
|
+
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
469
590
|
else
|
470
|
-
|
471
|
-
unless pk || sequence_name
|
472
|
-
pk, sequence_name = *pk_and_sequence_for(table)
|
473
|
-
end
|
474
|
-
|
475
|
-
# If a pk is given, fallback to default sequence name.
|
476
|
-
# Don't fetch last insert id for a table without a pk.
|
477
|
-
if pk && sequence_name ||= default_sequence_name(table, pk)
|
478
|
-
last_insert_id(table, sequence_name)
|
479
|
-
end
|
591
|
+
super
|
480
592
|
end
|
481
593
|
end
|
594
|
+
alias :create :insert
|
482
595
|
|
483
596
|
# create a 2D array representing the result set
|
484
597
|
def result_as_array(res) #:nodoc:
|
485
598
|
# check if we have any binary column and if they need escaping
|
486
|
-
|
487
|
-
|
488
|
-
# unescape string passed BYTEA field (OID == 17)
|
489
|
-
unescape_col << ( res.ftype(j)==17 )
|
599
|
+
ftypes = Array.new(res.nfields) do |i|
|
600
|
+
[i, res.ftype(i)]
|
490
601
|
end
|
491
602
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
603
|
+
rows = res.values
|
604
|
+
return rows unless ftypes.any? { |_, x|
|
605
|
+
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
|
606
|
+
}
|
607
|
+
|
608
|
+
typehash = ftypes.group_by { |_, type| type }
|
609
|
+
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
610
|
+
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
611
|
+
|
612
|
+
rows.each do |row|
|
613
|
+
# unescape string passed BYTEA field (OID == 17)
|
614
|
+
binaries.each do |index, _|
|
615
|
+
row[index] = unescape_bytea(row[index])
|
616
|
+
end
|
617
|
+
|
618
|
+
# If this is a money type column and there are any currency symbols,
|
619
|
+
# then strip them off. Indeed it would be prettier to do this in
|
620
|
+
# PostgreSQLColumn.string_to_decimal but would break form input
|
621
|
+
# fields that call value_before_type_cast.
|
622
|
+
monies.each do |index, _|
|
623
|
+
data = row[index]
|
624
|
+
# Because money output is formatted according to the locale, there are two
|
625
|
+
# cases to consider (note the decimal separators):
|
626
|
+
# (1) $12,345,678.12
|
627
|
+
# (2) $12.345.678,12
|
628
|
+
case data
|
629
|
+
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
630
|
+
data.gsub!(/[^-\d.]/, '')
|
631
|
+
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
632
|
+
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
633
|
+
end
|
499
634
|
end
|
500
635
|
end
|
501
|
-
return ary
|
502
636
|
end
|
503
637
|
|
504
638
|
|
505
639
|
# Queries the database and returns the results in an Array-like object
|
506
640
|
def query(sql, name = nil) #:nodoc:
|
507
641
|
log(sql, name) do
|
508
|
-
|
509
|
-
res = @connection.async_exec(sql)
|
510
|
-
else
|
511
|
-
res = @connection.exec(sql)
|
512
|
-
end
|
513
|
-
return result_as_array(res)
|
642
|
+
result_as_array @connection.async_exec(sql)
|
514
643
|
end
|
515
644
|
end
|
516
645
|
|
@@ -518,14 +647,48 @@ module ActiveRecord
|
|
518
647
|
# or raising a PGError exception otherwise.
|
519
648
|
def execute(sql, name = nil)
|
520
649
|
log(sql, name) do
|
521
|
-
|
522
|
-
@connection.async_exec(sql)
|
523
|
-
else
|
524
|
-
@connection.exec(sql)
|
525
|
-
end
|
650
|
+
@connection.async_exec(sql)
|
526
651
|
end
|
527
652
|
end
|
528
653
|
|
654
|
+
def substitute_at(column, index)
|
655
|
+
Arel::Nodes::BindParam.new "$#{index + 1}"
|
656
|
+
end
|
657
|
+
|
658
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
659
|
+
log(sql, name, binds) do
|
660
|
+
result = binds.empty? ? exec_no_cache(sql, binds) :
|
661
|
+
exec_cache(sql, binds)
|
662
|
+
|
663
|
+
ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
|
664
|
+
result.clear
|
665
|
+
return ret
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
def exec_delete(sql, name = 'SQL', binds = [])
|
670
|
+
log(sql, name, binds) do
|
671
|
+
result = binds.empty? ? exec_no_cache(sql, binds) :
|
672
|
+
exec_cache(sql, binds)
|
673
|
+
affected = result.cmd_tuples
|
674
|
+
result.clear
|
675
|
+
affected
|
676
|
+
end
|
677
|
+
end
|
678
|
+
alias :exec_update :exec_delete
|
679
|
+
|
680
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
681
|
+
unless pk
|
682
|
+
# Extract the table from the insert sql. Yuck.
|
683
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
684
|
+
pk = primary_key(table_ref) if table_ref
|
685
|
+
end
|
686
|
+
|
687
|
+
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
|
688
|
+
|
689
|
+
[sql, binds]
|
690
|
+
end
|
691
|
+
|
529
692
|
# Executes an UPDATE query and returns the number of affected tuples.
|
530
693
|
def update_sql(sql, name = nil)
|
531
694
|
super.cmd_tuples
|
@@ -546,12 +709,8 @@ module ActiveRecord
|
|
546
709
|
execute "ROLLBACK"
|
547
710
|
end
|
548
711
|
|
549
|
-
|
550
|
-
|
551
|
-
# while the ruby-postgres driver does not.
|
552
|
-
def outside_transaction?
|
553
|
-
@connection.transaction_status == PGconn::PQTRANS_IDLE
|
554
|
-
end
|
712
|
+
def outside_transaction?
|
713
|
+
@connection.transaction_status == PGconn::PQTRANS_IDLE
|
555
714
|
end
|
556
715
|
|
557
716
|
def create_savepoint
|
@@ -568,12 +727,14 @@ module ActiveRecord
|
|
568
727
|
|
569
728
|
# SCHEMA STATEMENTS ========================================
|
570
729
|
|
571
|
-
|
730
|
+
# Drops the database specified on the +name+ attribute
|
731
|
+
# and creates it again using the provided +options+.
|
732
|
+
def recreate_database(name, options = {}) #:nodoc:
|
572
733
|
drop_database(name)
|
573
|
-
create_database(name)
|
734
|
+
create_database(name, options)
|
574
735
|
end
|
575
736
|
|
576
|
-
# Create a new PostgreSQL database.
|
737
|
+
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
|
577
738
|
# <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
|
578
739
|
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
|
579
740
|
#
|
@@ -603,88 +764,112 @@ module ActiveRecord
|
|
603
764
|
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
|
604
765
|
end
|
605
766
|
|
606
|
-
# Drops a PostgreSQL database
|
767
|
+
# Drops a PostgreSQL database.
|
607
768
|
#
|
608
769
|
# Example:
|
609
770
|
# drop_database 'matt_development'
|
610
771
|
def drop_database(name) #:nodoc:
|
611
|
-
|
612
|
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
613
|
-
else
|
614
|
-
begin
|
615
|
-
execute "DROP DATABASE #{quote_table_name(name)}"
|
616
|
-
rescue ActiveRecord::StatementInvalid
|
617
|
-
@logger.warn "#{name} database doesn't exist." if @logger
|
618
|
-
end
|
619
|
-
end
|
772
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
620
773
|
end
|
621
774
|
|
622
|
-
|
623
775
|
# Returns the list of all tables in the schema search path or a specified schema.
|
624
776
|
def tables(name = nil)
|
625
|
-
|
626
|
-
query(<<-SQL, name).map { |row| row[0] }
|
777
|
+
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
627
778
|
SELECT tablename
|
628
|
-
|
629
|
-
|
779
|
+
FROM pg_tables
|
780
|
+
WHERE schemaname = ANY (current_schemas(false))
|
630
781
|
SQL
|
631
782
|
end
|
632
783
|
|
633
|
-
# Returns
|
784
|
+
# Returns true if table exists.
|
785
|
+
# If the schema is not specified as part of +name+ then it will only find tables within
|
786
|
+
# the current schema search path (regardless of permissions to access tables in other schemas)
|
787
|
+
def table_exists?(name)
|
788
|
+
schema, table = Utils.extract_schema_and_table(name.to_s)
|
789
|
+
return false unless table
|
790
|
+
|
791
|
+
binds = [[nil, table]]
|
792
|
+
binds << [nil, schema] if schema
|
793
|
+
|
794
|
+
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
795
|
+
SELECT COUNT(*)
|
796
|
+
FROM pg_class c
|
797
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
798
|
+
WHERE c.relkind in ('v','r')
|
799
|
+
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
|
800
|
+
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
|
801
|
+
SQL
|
802
|
+
end
|
803
|
+
|
804
|
+
# Returns true if schema exists.
|
805
|
+
def schema_exists?(name)
|
806
|
+
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
807
|
+
SELECT COUNT(*)
|
808
|
+
FROM pg_namespace
|
809
|
+
WHERE nspname = '#{name}'
|
810
|
+
SQL
|
811
|
+
end
|
812
|
+
|
813
|
+
# Returns an array of indexes for the given table.
|
634
814
|
def indexes(table_name, name = nil)
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
815
|
+
result = query(<<-SQL, 'SCHEMA')
|
816
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
817
|
+
FROM pg_class t
|
818
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
819
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
639
820
|
WHERE i.relkind = 'i'
|
640
|
-
AND d.indexrelid = i.oid
|
641
821
|
AND d.indisprimary = 'f'
|
642
|
-
AND t.oid = d.indrelid
|
643
822
|
AND t.relname = '#{table_name}'
|
644
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname
|
823
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
645
824
|
ORDER BY i.relname
|
646
825
|
SQL
|
647
826
|
|
648
827
|
|
649
|
-
|
650
|
-
|
651
|
-
indexes = result.map do |row|
|
828
|
+
result.map do |row|
|
652
829
|
index_name = row[0]
|
653
830
|
unique = row[1] == 't'
|
654
831
|
indkey = row[2].split(" ")
|
655
|
-
|
832
|
+
inddef = row[3]
|
833
|
+
oid = row[4]
|
656
834
|
|
657
|
-
columns = query(<<-SQL, "
|
658
|
-
SELECT a.
|
835
|
+
columns = Hash[query(<<-SQL, "SCHEMA")]
|
836
|
+
SELECT a.attnum, a.attname
|
659
837
|
FROM pg_attribute a
|
660
838
|
WHERE a.attrelid = #{oid}
|
661
839
|
AND a.attnum IN (#{indkey.join(",")})
|
662
840
|
SQL
|
663
841
|
|
664
|
-
column_names = indkey.
|
665
|
-
IndexDefinition.new(table_name, index_name, unique, column_names)
|
842
|
+
column_names = columns.values_at(*indkey).compact
|
666
843
|
|
667
|
-
|
844
|
+
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
845
|
+
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
846
|
+
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
668
847
|
|
669
|
-
|
848
|
+
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
849
|
+
end.compact
|
670
850
|
end
|
671
851
|
|
672
852
|
# Returns the list of all column definitions for a table.
|
673
853
|
def columns(table_name, name = nil)
|
674
854
|
# Limit, precision, and scale are all handled by the superclass.
|
675
|
-
column_definitions(table_name).collect do |
|
676
|
-
PostgreSQLColumn.new(
|
855
|
+
column_definitions(table_name).collect do |column_name, type, default, notnull|
|
856
|
+
PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
|
677
857
|
end
|
678
858
|
end
|
679
859
|
|
680
860
|
# Returns the current database name.
|
681
861
|
def current_database
|
682
|
-
query('select current_database()')[0][0]
|
862
|
+
query('select current_database()', 'SCHEMA')[0][0]
|
863
|
+
end
|
864
|
+
|
865
|
+
# Returns the current schema name.
|
866
|
+
def current_schema
|
867
|
+
query('SELECT current_schema', 'SCHEMA')[0][0]
|
683
868
|
end
|
684
869
|
|
685
870
|
# Returns the current database encoding format.
|
686
871
|
def encoding
|
687
|
-
query(<<-end_sql)[0][0]
|
872
|
+
query(<<-end_sql, 'SCHEMA')[0][0]
|
688
873
|
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
689
874
|
WHERE pg_database.datname LIKE '#{current_database}'
|
690
875
|
end_sql
|
@@ -697,49 +882,59 @@ module ActiveRecord
|
|
697
882
|
# This should be not be called manually but set in database.yml.
|
698
883
|
def schema_search_path=(schema_csv)
|
699
884
|
if schema_csv
|
700
|
-
execute
|
885
|
+
execute("SET search_path TO #{schema_csv}", 'SCHEMA')
|
701
886
|
@schema_search_path = schema_csv
|
702
887
|
end
|
703
888
|
end
|
704
889
|
|
705
890
|
# Returns the active schema search path.
|
706
891
|
def schema_search_path
|
707
|
-
@schema_search_path ||= query('SHOW search_path')[0][0]
|
892
|
+
@schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
|
708
893
|
end
|
709
894
|
|
710
895
|
# Returns the current client message level.
|
711
896
|
def client_min_messages
|
712
|
-
query('SHOW client_min_messages')[0][0]
|
897
|
+
query('SHOW client_min_messages', 'SCHEMA')[0][0]
|
713
898
|
end
|
714
899
|
|
715
900
|
# Set the client message level.
|
716
901
|
def client_min_messages=(level)
|
717
|
-
execute("SET client_min_messages TO '#{level}'")
|
902
|
+
execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
|
718
903
|
end
|
719
904
|
|
720
905
|
# Returns the sequence name for a table's primary key or some other specified key.
|
721
906
|
def default_sequence_name(table_name, pk = nil) #:nodoc:
|
722
|
-
|
723
|
-
|
907
|
+
serial_sequence(table_name, pk || 'id').split('.').last
|
908
|
+
rescue ActiveRecord::StatementInvalid
|
909
|
+
"#{table_name}_#{pk || 'id'}_seq"
|
910
|
+
end
|
911
|
+
|
912
|
+
def serial_sequence(table, column)
|
913
|
+
result = exec_query(<<-eosql, 'SCHEMA')
|
914
|
+
SELECT pg_get_serial_sequence('#{table}', '#{column}')
|
915
|
+
eosql
|
916
|
+
result.rows.first.first
|
724
917
|
end
|
725
918
|
|
726
919
|
# Resets the sequence of a table's primary key to the maximum value.
|
727
920
|
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
728
921
|
unless pk and sequence
|
729
922
|
default_pk, default_sequence = pk_and_sequence_for(table)
|
923
|
+
|
730
924
|
pk ||= default_pk
|
731
925
|
sequence ||= default_sequence
|
732
926
|
end
|
733
|
-
if pk
|
734
|
-
if sequence
|
735
|
-
quoted_sequence = quote_column_name(sequence)
|
736
927
|
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
928
|
+
if @logger && pk && !sequence
|
929
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence"
|
930
|
+
end
|
931
|
+
|
932
|
+
if pk && sequence
|
933
|
+
quoted_sequence = quote_table_name(sequence)
|
934
|
+
|
935
|
+
select_value <<-end_sql, 'SCHEMA'
|
936
|
+
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
937
|
+
end_sql
|
743
938
|
end
|
744
939
|
end
|
745
940
|
|
@@ -747,7 +942,7 @@ module ActiveRecord
|
|
747
942
|
def pk_and_sequence_for(table) #:nodoc:
|
748
943
|
# First try looking for a sequence with a dependency on the
|
749
944
|
# given table's primary key.
|
750
|
-
result = query(<<-end_sql, '
|
945
|
+
result = query(<<-end_sql, 'SCHEMA')[0]
|
751
946
|
SELECT attr.attname, seq.relname
|
752
947
|
FROM pg_class seq,
|
753
948
|
pg_attribute attr,
|
@@ -765,16 +960,13 @@ module ActiveRecord
|
|
765
960
|
end_sql
|
766
961
|
|
767
962
|
if result.nil? or result.empty?
|
768
|
-
|
769
|
-
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
770
|
-
# the 8.1+ nextval('foo'::regclass).
|
771
|
-
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
963
|
+
result = query(<<-end_sql, 'SCHEMA')[0]
|
772
964
|
SELECT attr.attname,
|
773
965
|
CASE
|
774
|
-
WHEN split_part(def.
|
775
|
-
substr(split_part(def.
|
776
|
-
strpos(split_part(def.
|
777
|
-
ELSE split_part(def.
|
966
|
+
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
967
|
+
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
968
|
+
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
969
|
+
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
778
970
|
END
|
779
971
|
FROM pg_class t
|
780
972
|
JOIN pg_attribute attr ON (t.oid = attrelid)
|
@@ -782,11 +974,10 @@ module ActiveRecord
|
|
782
974
|
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
783
975
|
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
784
976
|
AND cons.contype = 'p'
|
785
|
-
AND def.
|
977
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
|
786
978
|
end_sql
|
787
979
|
end
|
788
980
|
|
789
|
-
# [primary_key, sequence]
|
790
981
|
[result.first, result.last]
|
791
982
|
rescue
|
792
983
|
nil
|
@@ -794,49 +985,49 @@ module ActiveRecord
|
|
794
985
|
|
795
986
|
# Returns just a table's primary key
|
796
987
|
def primary_key(table)
|
797
|
-
|
798
|
-
|
988
|
+
row = exec_query(<<-end_sql, 'SCHEMA').rows.first
|
989
|
+
SELECT attr.attname
|
990
|
+
FROM pg_attribute attr
|
991
|
+
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
|
992
|
+
WHERE cons.contype = 'p'
|
993
|
+
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
|
994
|
+
end_sql
|
995
|
+
|
996
|
+
row && row.first
|
799
997
|
end
|
800
998
|
|
801
999
|
# Renames a table.
|
1000
|
+
# Also renames a table's primary key sequence if the sequence name matches the
|
1001
|
+
# Active Record default.
|
1002
|
+
#
|
1003
|
+
# Example:
|
1004
|
+
# rename_table('octopuses', 'octopi')
|
802
1005
|
def rename_table(name, new_name)
|
1006
|
+
clear_cache!
|
803
1007
|
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
1008
|
+
pk, seq = pk_and_sequence_for(new_name)
|
1009
|
+
if seq == "#{name}_#{pk}_seq"
|
1010
|
+
new_seq = "#{new_name}_#{pk}_seq"
|
1011
|
+
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
1012
|
+
end
|
804
1013
|
end
|
805
1014
|
|
806
1015
|
# Adds a new column to the named table.
|
807
1016
|
# See TableDefinition#column for details of the options you can use.
|
808
1017
|
def add_column(table_name, column_name, type, options = {})
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
# Add the column.
|
813
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
|
1018
|
+
clear_cache!
|
1019
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1020
|
+
add_column_options!(add_column_sql, options)
|
814
1021
|
|
815
|
-
|
816
|
-
change_column_null(table_name, column_name, false, default) if notnull
|
1022
|
+
execute add_column_sql
|
817
1023
|
end
|
818
1024
|
|
819
1025
|
# Changes the column of a table.
|
820
1026
|
def change_column(table_name, column_name, type, options = {})
|
1027
|
+
clear_cache!
|
821
1028
|
quoted_table_name = quote_table_name(table_name)
|
822
1029
|
|
823
|
-
|
824
|
-
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
825
|
-
rescue ActiveRecord::StatementInvalid => e
|
826
|
-
raise e if postgresql_version > 80000
|
827
|
-
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
|
828
|
-
begin
|
829
|
-
begin_db_transaction
|
830
|
-
tmp_column_name = "#{column_name}_ar_tmp"
|
831
|
-
add_column(table_name, tmp_column_name, type, options)
|
832
|
-
execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
|
833
|
-
remove_column(table_name, column_name)
|
834
|
-
rename_column(table_name, tmp_column_name, column_name)
|
835
|
-
commit_db_transaction
|
836
|
-
rescue
|
837
|
-
rollback_db_transaction
|
838
|
-
end
|
839
|
-
end
|
1030
|
+
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
840
1031
|
|
841
1032
|
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
842
1033
|
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
@@ -844,10 +1035,12 @@ module ActiveRecord
|
|
844
1035
|
|
845
1036
|
# Changes the default value of a table column.
|
846
1037
|
def change_column_default(table_name, column_name, default)
|
1038
|
+
clear_cache!
|
847
1039
|
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
848
1040
|
end
|
849
1041
|
|
850
1042
|
def change_column_null(table_name, column_name, null, default = nil)
|
1043
|
+
clear_cache!
|
851
1044
|
unless null || default.nil?
|
852
1045
|
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
853
1046
|
end
|
@@ -856,6 +1049,7 @@ module ActiveRecord
|
|
856
1049
|
|
857
1050
|
# Renames a column in a table.
|
858
1051
|
def rename_column(table_name, column_name, new_column_name)
|
1052
|
+
clear_cache!
|
859
1053
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
860
1054
|
end
|
861
1055
|
|
@@ -863,19 +1057,42 @@ module ActiveRecord
|
|
863
1057
|
execute "DROP INDEX #{quote_table_name(index_name)}"
|
864
1058
|
end
|
865
1059
|
|
1060
|
+
def rename_index(table_name, old_name, new_name)
|
1061
|
+
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
1062
|
+
end
|
1063
|
+
|
866
1064
|
def index_name_length
|
867
1065
|
63
|
868
1066
|
end
|
869
1067
|
|
870
1068
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
871
1069
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
when
|
878
|
-
else raise(ActiveRecordError, "No
|
1070
|
+
case type.to_s
|
1071
|
+
when 'binary'
|
1072
|
+
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
1073
|
+
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
|
1074
|
+
case limit
|
1075
|
+
when nil, 0..0x3fffffff; super(type)
|
1076
|
+
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
1077
|
+
end
|
1078
|
+
when 'text'
|
1079
|
+
# PostgreSQL doesn't support limits on text columns.
|
1080
|
+
# The hard limit is 1Gb, according to section 8.3 in the manual.
|
1081
|
+
case limit
|
1082
|
+
when nil, 0..0x3fffffff; super(type)
|
1083
|
+
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
1084
|
+
end
|
1085
|
+
when 'integer'
|
1086
|
+
return 'integer' unless limit
|
1087
|
+
|
1088
|
+
case limit
|
1089
|
+
when 1, 2; 'smallint'
|
1090
|
+
when 3, 4; 'integer'
|
1091
|
+
when 5..8; 'bigint'
|
1092
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
1093
|
+
end
|
1094
|
+
else
|
1095
|
+
super
|
879
1096
|
end
|
880
1097
|
end
|
881
1098
|
|
@@ -885,153 +1102,172 @@ module ActiveRecord
|
|
885
1102
|
# requires that the ORDER BY include the distinct column.
|
886
1103
|
#
|
887
1104
|
# distinct("posts.id", "posts.created_at desc")
|
888
|
-
def distinct(columns,
|
889
|
-
return "DISTINCT #{columns}" if
|
1105
|
+
def distinct(columns, orders) #:nodoc:
|
1106
|
+
return "DISTINCT #{columns}" if orders.empty?
|
890
1107
|
|
891
1108
|
# Construct a clean list of column names from the ORDER BY clause, removing
|
892
1109
|
# any ASC/DESC modifiers
|
893
|
-
order_columns =
|
894
|
-
order_columns.delete_if
|
1110
|
+
order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
|
1111
|
+
order_columns.delete_if { |c| c.blank? }
|
895
1112
|
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
896
1113
|
|
897
|
-
|
898
|
-
# all the required columns for the ORDER BY to work properly.
|
899
|
-
sql = "DISTINCT ON (#{columns}) #{columns}, "
|
900
|
-
sql << order_columns * ', '
|
1114
|
+
"DISTINCT #{columns}, #{order_columns * ', '}"
|
901
1115
|
end
|
902
1116
|
|
903
|
-
|
904
|
-
|
905
|
-
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
|
906
|
-
# by wrapping the +sql+ string as a sub-select and ordering in that query.
|
907
|
-
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
|
908
|
-
return sql if options[:order].blank?
|
909
|
-
|
910
|
-
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
911
|
-
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
|
912
|
-
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
|
1117
|
+
module Utils
|
1118
|
+
extend self
|
913
1119
|
|
914
|
-
|
1120
|
+
# Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
|
1121
|
+
# +schema_name+ is nil if not specified in +name+.
|
1122
|
+
# +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
|
1123
|
+
# +name+ supports the range of schema/table references understood by PostgreSQL, for example:
|
1124
|
+
#
|
1125
|
+
# * <tt>table_name</tt>
|
1126
|
+
# * <tt>"table.name"</tt>
|
1127
|
+
# * <tt>schema_name.table_name</tt>
|
1128
|
+
# * <tt>schema_name."table.name"</tt>
|
1129
|
+
# * <tt>"schema.name"."table name"</tt>
|
1130
|
+
def extract_schema_and_table(name)
|
1131
|
+
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
|
1132
|
+
[schema, table]
|
1133
|
+
end
|
915
1134
|
end
|
916
1135
|
|
917
1136
|
protected
|
918
|
-
# Returns the version of the connected PostgreSQL
|
1137
|
+
# Returns the version of the connected PostgreSQL server.
|
919
1138
|
def postgresql_version
|
920
|
-
@
|
921
|
-
|
922
|
-
|
1139
|
+
@connection.server_version
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
|
1143
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
1144
|
+
UNIQUE_VIOLATION = "23505"
|
1145
|
+
|
1146
|
+
def translate_exception(exception, message)
|
1147
|
+
return exception unless exception.respond_to?(:result)
|
1148
|
+
|
1149
|
+
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
|
1150
|
+
when UNIQUE_VIOLATION
|
1151
|
+
RecordNotUnique.new(message, exception)
|
1152
|
+
when FOREIGN_KEY_VIOLATION
|
1153
|
+
InvalidForeignKey.new(message, exception)
|
1154
|
+
else
|
1155
|
+
super
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
private
|
1160
|
+
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
1161
|
+
|
1162
|
+
def exec_no_cache(sql, binds)
|
1163
|
+
@connection.async_exec(sql, [])
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
def exec_cache(sql, binds)
|
1167
|
+
begin
|
1168
|
+
stmt_key = prepare_statement sql
|
1169
|
+
|
1170
|
+
# Clear the queue
|
1171
|
+
@connection.get_last_result
|
1172
|
+
@connection.send_query_prepared(stmt_key, binds.map { |col, val|
|
1173
|
+
type_cast(val, col)
|
1174
|
+
})
|
1175
|
+
@connection.block
|
1176
|
+
@connection.get_last_result
|
1177
|
+
rescue PGError => e
|
1178
|
+
# Get the PG code for the failure. Annoyingly, the code for
|
1179
|
+
# prepared statements whose return value may have changed is
|
1180
|
+
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
1181
|
+
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1182
|
+
begin
|
1183
|
+
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
1184
|
+
rescue
|
1185
|
+
raise e
|
1186
|
+
end
|
1187
|
+
if FEATURE_NOT_SUPPORTED == code
|
1188
|
+
@statements.delete sql_key(sql)
|
1189
|
+
retry
|
923
1190
|
else
|
924
|
-
|
925
|
-
begin
|
926
|
-
query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
|
927
|
-
($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
|
928
|
-
rescue
|
929
|
-
0
|
930
|
-
end
|
1191
|
+
raise e
|
931
1192
|
end
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
# Returns the statement identifier for the client side cache
|
1197
|
+
# of statements
|
1198
|
+
def sql_key(sql)
|
1199
|
+
"#{schema_search_path}-#{sql}"
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
# Prepare the statement if it hasn't been prepared, return
|
1203
|
+
# the statement key.
|
1204
|
+
def prepare_statement(sql)
|
1205
|
+
sql_key = sql_key(sql)
|
1206
|
+
unless @statements.key? sql_key
|
1207
|
+
nextkey = @statements.next_key
|
1208
|
+
@connection.prepare nextkey, sql
|
1209
|
+
@statements[sql_key] = nextkey
|
1210
|
+
end
|
1211
|
+
@statements[sql_key]
|
932
1212
|
end
|
933
1213
|
|
934
|
-
private
|
935
1214
|
# The internal PostgreSQL identifier of the money data type.
|
936
1215
|
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
1216
|
+
# The internal PostgreSQL identifier of the BYTEA data type.
|
1217
|
+
BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
|
937
1218
|
|
938
1219
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
939
1220
|
# connected server's characteristics.
|
940
1221
|
def connect
|
941
1222
|
@connection = PGconn.connect(*@connection_parameters)
|
942
|
-
PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
|
943
|
-
|
944
|
-
# Ignore async_exec and async_query when using postgres-pr.
|
945
|
-
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
946
1223
|
|
947
1224
|
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
948
1225
|
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
949
1226
|
# should know about this but can't detect it there, so deal with it here.
|
950
|
-
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
951
|
-
PostgreSQLColumn.module_eval(<<-end_eval)
|
952
|
-
def extract_precision(sql_type) # def extract_precision(sql_type)
|
953
|
-
if sql_type =~ /^money$/ # if sql_type =~ /^money$/
|
954
|
-
#{money_precision} # 19
|
955
|
-
else # else
|
956
|
-
super # super
|
957
|
-
end # end
|
958
|
-
end # end
|
959
|
-
end_eval
|
1227
|
+
PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
|
960
1228
|
|
961
1229
|
configure_connection
|
962
1230
|
end
|
963
1231
|
|
964
|
-
# Configures the encoding, verbosity,
|
1232
|
+
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
965
1233
|
# This is called by #connect and should not be called manually.
|
966
1234
|
def configure_connection
|
967
1235
|
if @config[:encoding]
|
968
|
-
|
969
|
-
@connection.set_client_encoding(@config[:encoding])
|
970
|
-
else
|
971
|
-
execute("SET client_encoding TO '#{@config[:encoding]}'")
|
972
|
-
end
|
1236
|
+
@connection.set_client_encoding(@config[:encoding])
|
973
1237
|
end
|
974
1238
|
self.client_min_messages = @config[:min_messages] if @config[:min_messages]
|
975
1239
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
976
1240
|
|
977
1241
|
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
|
978
1242
|
set_standard_conforming_strings
|
1243
|
+
|
1244
|
+
# If using Active Record's time zone support configure the connection to return
|
1245
|
+
# TIMESTAMP WITH ZONE types in UTC.
|
1246
|
+
if ActiveRecord::Base.default_timezone == :utc
|
1247
|
+
execute("SET time zone 'UTC'", 'SCHEMA')
|
1248
|
+
elsif @local_tz
|
1249
|
+
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
|
1250
|
+
end
|
979
1251
|
end
|
980
1252
|
|
981
1253
|
# Returns the current ID of a table's sequence.
|
982
|
-
def last_insert_id(
|
983
|
-
|
1254
|
+
def last_insert_id(sequence_name) #:nodoc:
|
1255
|
+
r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1256
|
+
Integer(r.rows.first.first)
|
984
1257
|
end
|
985
1258
|
|
986
1259
|
# Executes a SELECT query and returns the results, performing any data type
|
987
1260
|
# conversions that are required to be performed here instead of in PostgreSQLColumn.
|
988
|
-
def select(sql, name = nil)
|
989
|
-
|
990
|
-
result = []
|
991
|
-
for row in rows
|
992
|
-
row_hash = {}
|
993
|
-
fields.each_with_index do |f, i|
|
994
|
-
row_hash[f] = row[i]
|
995
|
-
end
|
996
|
-
result << row_hash
|
997
|
-
end
|
998
|
-
result
|
1261
|
+
def select(sql, name = nil, binds = [])
|
1262
|
+
exec_query(sql, name, binds).to_a
|
999
1263
|
end
|
1000
1264
|
|
1001
1265
|
def select_raw(sql, name = nil)
|
1002
1266
|
res = execute(sql, name)
|
1003
1267
|
results = result_as_array(res)
|
1004
|
-
fields =
|
1005
|
-
rows = []
|
1006
|
-
if res.ntuples > 0
|
1007
|
-
fields = res.fields
|
1008
|
-
results.each do |row|
|
1009
|
-
hashed_row = {}
|
1010
|
-
row.each_index do |cell_index|
|
1011
|
-
# If this is a money type column and there are any currency symbols,
|
1012
|
-
# then strip them off. Indeed it would be prettier to do this in
|
1013
|
-
# PostgreSQLColumn.string_to_decimal but would break form input
|
1014
|
-
# fields that call value_before_type_cast.
|
1015
|
-
if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
|
1016
|
-
# Because money output is formatted according to the locale, there are two
|
1017
|
-
# cases to consider (note the decimal separators):
|
1018
|
-
# (1) $12,345,678.12
|
1019
|
-
# (2) $12.345.678,12
|
1020
|
-
case column = row[cell_index]
|
1021
|
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
1022
|
-
row[cell_index] = column.gsub(/[^-\d\.]/, '')
|
1023
|
-
when /^-?\D+[\d\.]+,\d{2}$/ # (2)
|
1024
|
-
row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
|
1025
|
-
end
|
1026
|
-
end
|
1027
|
-
|
1028
|
-
hashed_row[fields[cell_index]] = column
|
1029
|
-
end
|
1030
|
-
rows << row
|
1031
|
-
end
|
1032
|
-
end
|
1268
|
+
fields = res.fields
|
1033
1269
|
res.clear
|
1034
|
-
return fields,
|
1270
|
+
return fields, results
|
1035
1271
|
end
|
1036
1272
|
|
1037
1273
|
# Returns the list of a table's column names, data types, and default values.
|
@@ -1053,8 +1289,9 @@ module ActiveRecord
|
|
1053
1289
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1054
1290
|
# - ::regclass is a function that gives the id for a table name
|
1055
1291
|
def column_definitions(table_name) #:nodoc:
|
1056
|
-
|
1057
|
-
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1292
|
+
exec_query(<<-end_sql, 'SCHEMA').rows
|
1293
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1294
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
1058
1295
|
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
1059
1296
|
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
1060
1297
|
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
@@ -1064,15 +1301,23 @@ module ActiveRecord
|
|
1064
1301
|
end
|
1065
1302
|
|
1066
1303
|
def extract_pg_identifier_from_name(name)
|
1067
|
-
match_data = name
|
1304
|
+
match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
|
1068
1305
|
|
1069
1306
|
if match_data
|
1070
|
-
rest = name[match_data[0].length
|
1071
|
-
rest = rest[1
|
1307
|
+
rest = name[match_data[0].length, name.length]
|
1308
|
+
rest = rest[1, rest.length] if rest.start_with? "."
|
1072
1309
|
[match_data[1], (rest.length > 0 ? rest : nil)]
|
1073
1310
|
end
|
1074
1311
|
end
|
1312
|
+
|
1313
|
+
def extract_table_ref_from_insert_sql(sql)
|
1314
|
+
sql[/into\s+([^\(]*).*values\s*\(/i]
|
1315
|
+
$1.strip if $1
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
def table_definition
|
1319
|
+
TableDefinition.new(self)
|
1320
|
+
end
|
1075
1321
|
end
|
1076
1322
|
end
|
1077
1323
|
end
|
1078
|
-
|