activerecord 3.2.6 → 6.0.0
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 +611 -6417
- data/MIT-LICENSE +4 -2
- data/README.rdoc +44 -47
- data/examples/performance.rb +79 -71
- data/examples/simple.rb +6 -5
- data/lib/active_record/aggregations.rb +268 -238
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -42
- data/lib/active_record/associations/association.rb +173 -81
- data/lib/active_record/associations/association_scope.rb +124 -92
- data/lib/active_record/associations/belongs_to_association.rb +83 -38
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +11 -9
- data/lib/active_record/associations/builder/association.rb +113 -32
- data/lib/active_record/associations/builder/belongs_to.rb +105 -60
- data/lib/active_record/associations/builder/collection_association.rb +53 -56
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +98 -41
- data/lib/active_record/associations/builder/has_many.rb +11 -63
- data/lib/active_record/associations/builder/has_one.rb +47 -45
- data/lib/active_record/associations/builder/singular_association.rb +30 -18
- data/lib/active_record/associations/collection_association.rb +217 -295
- data/lib/active_record/associations/collection_proxy.rb +1074 -77
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +78 -50
- data/lib/active_record/associations/has_many_through_association.rb +99 -61
- data/lib/active_record/associations/has_one_association.rb +75 -30
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +45 -119
- data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
- data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
- data/lib/active_record/associations/join_dependency.rb +208 -164
- data/lib/active_record/associations/preloader/association.rb +93 -87
- data/lib/active_record/associations/preloader/through_association.rb +87 -38
- data/lib/active_record/associations/preloader.rb +134 -110
- data/lib/active_record/associations/singular_association.rb +19 -24
- data/lib/active_record/associations/through_association.rb +61 -27
- data/lib/active_record/associations.rb +1766 -1505
- data/lib/active_record/attribute_assignment.rb +57 -193
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +58 -8
- data/lib/active_record/attribute_methods/dirty.rb +187 -67
- data/lib/active_record/attribute_methods/primary_key.rb +100 -78
- data/lib/active_record/attribute_methods/query.rb +10 -8
- data/lib/active_record/attribute_methods/read.rb +29 -118
- data/lib/active_record/attribute_methods/serialization.rb +60 -72
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -42
- data/lib/active_record/attribute_methods/write.rb +36 -44
- data/lib/active_record/attribute_methods.rb +306 -161
- data/lib/active_record/attributes.rb +279 -0
- data/lib/active_record/autosave_association.rb +324 -238
- data/lib/active_record/base.rb +114 -507
- data/lib/active_record/callbacks.rb +147 -83
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +32 -23
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +962 -279
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +32 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +331 -209
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +201 -65
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +510 -289
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +93 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1182 -313
- data/lib/active_record/connection_adapters/abstract/transaction.rb +323 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +585 -120
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +610 -463
- data/lib/active_record/connection_adapters/column.rb +58 -233
- data/lib/active_record/connection_adapters/connection_specification.rb +297 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +75 -207
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +182 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +113 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +205 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +222 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +776 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +695 -1052
- data/lib/active_record/connection_adapters/schema_cache.rb +115 -24
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +528 -26
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +267 -0
- data/lib/active_record/core.rb +599 -0
- data/lib/active_record/counter_cache.rb +177 -103
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +79 -0
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +107 -64
- data/lib/active_record/enum.rb +274 -0
- data/lib/active_record/errors.rb +254 -61
- data/lib/active_record/explain.rb +35 -70
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +18 -8
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixture_set/model_metadata.rb +33 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +153 -0
- data/lib/active_record/fixture_set/table_rows.rb +47 -0
- data/lib/active_record/fixtures.rb +291 -475
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +219 -100
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +175 -17
- data/lib/active_record/internal_metadata.rb +53 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +106 -92
- data/lib/active_record/locking/pessimistic.rb +23 -11
- data/lib/active_record/log_subscriber.rb +80 -30
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/migration/command_recorder.rb +235 -56
- data/lib/active_record/migration/compatibility.rb +244 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/migration.rb +917 -301
- data/lib/active_record/model_schema.rb +351 -175
- data/lib/active_record/nested_attributes.rb +366 -235
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +761 -166
- data/lib/active_record/query_cache.rb +22 -44
- data/lib/active_record/querying.rb +55 -31
- data/lib/active_record/railtie.rb +185 -47
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -4
- data/lib/active_record/railties/controller_runtime.rb +35 -33
- data/lib/active_record/railties/databases.rake +366 -463
- data/lib/active_record/readonly_attributes.rb +4 -6
- data/lib/active_record/reflection.rb +736 -228
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +252 -52
- data/lib/active_record/relation/calculations.rb +340 -270
- data/lib/active_record/relation/delegation.rb +117 -36
- data/lib/active_record/relation/finder_methods.rb +439 -286
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +184 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +49 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder.rb +131 -39
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +1163 -221
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +49 -120
- data/lib/active_record/relation/where_clause.rb +190 -0
- data/lib/active_record/relation/where_clause_factory.rb +33 -0
- data/lib/active_record/relation.rb +671 -349
- data/lib/active_record/result.rb +149 -15
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +153 -133
- data/lib/active_record/schema.rb +22 -19
- data/lib/active_record/schema_dumper.rb +178 -112
- data/lib/active_record/schema_migration.rb +60 -0
- data/lib/active_record/scoping/default.rb +107 -98
- data/lib/active_record/scoping/named.rb +130 -115
- data/lib/active_record/scoping.rb +77 -123
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +10 -6
- data/lib/active_record/statement_cache.rb +148 -0
- data/lib/active_record/store.rb +256 -16
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +75 -0
- data/lib/active_record/tasks/database_tasks.rb +506 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +141 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +77 -0
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +224 -0
- data/lib/active_record/timestamp.rb +93 -39
- data/lib/active_record/touch_later.rb +66 -0
- data/lib/active_record/transactions.rb +260 -129
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type/adapter_specific_registry.rb +129 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type.rb +78 -0
- data/lib/active_record/type_caster/connection.rb +34 -0
- data/lib/active_record/type_caster/map.rb +20 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +35 -18
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +123 -77
- data/lib/active_record/validations.rb +54 -43
- data/lib/active_record/version.rb +7 -7
- data/lib/active_record.rb +97 -49
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +18 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +68 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +257 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/depth_first.rb +204 -0
- data/lib/arel/visitors/dot.rb +297 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +157 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +159 -0
- data/lib/arel/visitors/oracle12.rb +66 -0
- data/lib/arel/visitors/postgresql.rb +110 -0
- data/lib/arel/visitors/sqlite.rb +39 -0
- data/lib/arel/visitors/to_sql.rb +889 -0
- data/lib/arel/visitors/visitor.rb +46 -0
- data/lib/arel/visitors/where_sql.rb +23 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +51 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +59 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +41 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +24 -22
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
- data/lib/rails/generators/active_record.rb +10 -16
- metadata +285 -149
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -188
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -426
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -579
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,250 +1,154 @@
|
|
1
|
-
|
2
|
-
require 'active_support/core_ext/object/blank'
|
3
|
-
require 'active_record/connection_adapters/statement_pool'
|
4
|
-
require 'arel/visitors/bind_visitor'
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
# Make sure we're using pg high enough for
|
7
|
-
gem
|
8
|
-
require
|
3
|
+
# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
|
4
|
+
gem "pg", ">= 0.18", "< 2.0"
|
5
|
+
require "pg"
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
host = config[:host]
|
16
|
-
port = config[:port] || 5432
|
17
|
-
username = config[:username].to_s if config[:username]
|
18
|
-
password = config[:password].to_s if config[:password]
|
19
|
-
|
20
|
-
if config.key?(:database)
|
21
|
-
database = config[:database]
|
22
|
-
else
|
23
|
-
raise ArgumentError, "No database specified. Missing argument: database."
|
24
|
-
end
|
25
|
-
|
26
|
-
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
27
|
-
# so just pass a nil connection object for the time being.
|
28
|
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
|
29
|
-
end
|
7
|
+
# Use async_exec instead of exec_params on pg versions before 1.1
|
8
|
+
class ::PG::Connection # :nodoc:
|
9
|
+
unless self.public_method_defined?(:async_exec_params)
|
10
|
+
remove_method :exec_params
|
11
|
+
alias exec_params async_exec
|
30
12
|
end
|
13
|
+
end
|
31
14
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
15
|
+
require "active_record/connection_adapters/abstract_adapter"
|
16
|
+
require "active_record/connection_adapters/statement_pool"
|
17
|
+
require "active_record/connection_adapters/postgresql/column"
|
18
|
+
require "active_record/connection_adapters/postgresql/database_statements"
|
19
|
+
require "active_record/connection_adapters/postgresql/explain_pretty_printer"
|
20
|
+
require "active_record/connection_adapters/postgresql/oid"
|
21
|
+
require "active_record/connection_adapters/postgresql/quoting"
|
22
|
+
require "active_record/connection_adapters/postgresql/referential_integrity"
|
23
|
+
require "active_record/connection_adapters/postgresql/schema_creation"
|
24
|
+
require "active_record/connection_adapters/postgresql/schema_definitions"
|
25
|
+
require "active_record/connection_adapters/postgresql/schema_dumper"
|
26
|
+
require "active_record/connection_adapters/postgresql/schema_statements"
|
27
|
+
require "active_record/connection_adapters/postgresql/type_metadata"
|
28
|
+
require "active_record/connection_adapters/postgresql/utils"
|
45
29
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
# :startdoc:
|
55
|
-
|
56
|
-
private
|
57
|
-
def extract_limit(sql_type)
|
58
|
-
case sql_type
|
59
|
-
when /^bigint/i; 8
|
60
|
-
when /^smallint/i; 2
|
61
|
-
else super
|
62
|
-
end
|
63
|
-
end
|
30
|
+
module ActiveRecord
|
31
|
+
module ConnectionHandling # :nodoc:
|
32
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
33
|
+
def postgresql_connection(config)
|
34
|
+
conn_params = config.symbolize_keys
|
64
35
|
|
65
|
-
|
66
|
-
def extract_scale(sql_type)
|
67
|
-
# Money type has a fixed scale of 2.
|
68
|
-
sql_type =~ /^money/ ? 2 : super
|
69
|
-
end
|
36
|
+
conn_params.delete_if { |_, v| v.nil? }
|
70
37
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
self.class.money_precision
|
75
|
-
else
|
76
|
-
super
|
77
|
-
end
|
78
|
-
end
|
38
|
+
# Map ActiveRecords param names to PGs.
|
39
|
+
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
40
|
+
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
79
41
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
# Numeric and monetary types
|
84
|
-
when /^(?:real|double precision)$/
|
85
|
-
:float
|
86
|
-
# Monetary types
|
87
|
-
when 'money'
|
88
|
-
:decimal
|
89
|
-
# Character types
|
90
|
-
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
91
|
-
:string
|
92
|
-
# Binary data types
|
93
|
-
when 'bytea'
|
94
|
-
:binary
|
95
|
-
# Date/time types
|
96
|
-
when /^timestamp with(?:out)? time zone$/
|
97
|
-
:datetime
|
98
|
-
when 'interval'
|
99
|
-
:string
|
100
|
-
# Geometric types
|
101
|
-
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
102
|
-
:string
|
103
|
-
# Network address types
|
104
|
-
when /^(?:cidr|inet|macaddr)$/
|
105
|
-
:string
|
106
|
-
# Bit strings
|
107
|
-
when /^bit(?: varying)?(?:\(\d+\))?$/
|
108
|
-
:string
|
109
|
-
# XML type
|
110
|
-
when 'xml'
|
111
|
-
:xml
|
112
|
-
# tsvector type
|
113
|
-
when 'tsvector'
|
114
|
-
:tsvector
|
115
|
-
# Arrays
|
116
|
-
when /^\D+\[\]$/
|
117
|
-
:string
|
118
|
-
# Object identifier types
|
119
|
-
when 'oid'
|
120
|
-
:integer
|
121
|
-
# UUID type
|
122
|
-
when 'uuid'
|
123
|
-
:string
|
124
|
-
# Small and big integer types
|
125
|
-
when /^(?:small|big)int$/
|
126
|
-
:integer
|
127
|
-
# Pass through all types that are not specific to PostgreSQL.
|
128
|
-
else
|
129
|
-
super
|
130
|
-
end
|
131
|
-
end
|
42
|
+
# Forward only valid config params to PG::Connection.connect.
|
43
|
+
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
44
|
+
conn_params.slice!(*valid_conn_param_keys)
|
132
45
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
# makes this method very very slow.
|
142
|
-
when NilClass
|
143
|
-
nil
|
144
|
-
# Numeric types
|
145
|
-
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
|
146
|
-
$1
|
147
|
-
# Character types
|
148
|
-
when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
|
149
|
-
$1
|
150
|
-
# Character types (8.1 formatting)
|
151
|
-
when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
|
152
|
-
$1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
|
153
|
-
# Binary data types
|
154
|
-
when /\A'(.*)'::bytea\z/m
|
155
|
-
$1
|
156
|
-
# Date/time types
|
157
|
-
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
158
|
-
$1
|
159
|
-
when /\A'(.*)'::interval\z/
|
160
|
-
$1
|
161
|
-
# Boolean type
|
162
|
-
when 'true'
|
163
|
-
true
|
164
|
-
when 'false'
|
165
|
-
false
|
166
|
-
# Geometric types
|
167
|
-
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
168
|
-
$1
|
169
|
-
# Network address types
|
170
|
-
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
171
|
-
$1
|
172
|
-
# Bit string types
|
173
|
-
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
174
|
-
$1
|
175
|
-
# XML type
|
176
|
-
when /\A'(.*)'::xml\z/m
|
177
|
-
$1
|
178
|
-
# Arrays
|
179
|
-
when /\A'(.*)'::"?\D+"?\[\]\z/
|
180
|
-
$1
|
181
|
-
# Object identifier types
|
182
|
-
when /\A-?\d+\z/
|
183
|
-
$1
|
184
|
-
else
|
185
|
-
# Anything else is blank, some user type, or some function
|
186
|
-
# and we can't know the value of that, so return nil.
|
187
|
-
nil
|
188
|
-
end
|
189
|
-
end
|
46
|
+
conn = PG.connect(conn_params)
|
47
|
+
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
|
48
|
+
rescue ::PG::Error => error
|
49
|
+
if error.message.include?(conn_params[:dbname])
|
50
|
+
raise ActiveRecord::NoDatabaseError
|
51
|
+
else
|
52
|
+
raise
|
53
|
+
end
|
190
54
|
end
|
55
|
+
end
|
191
56
|
|
192
|
-
|
193
|
-
#
|
57
|
+
module ConnectionAdapters
|
58
|
+
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
|
194
59
|
#
|
195
60
|
# Options:
|
196
61
|
#
|
197
|
-
# * <tt>:host</tt> - Defaults to
|
62
|
+
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
|
63
|
+
# the default is to connect to localhost.
|
198
64
|
# * <tt>:port</tt> - Defaults to 5432.
|
199
|
-
# * <tt>:username</tt> - Defaults to
|
200
|
-
# * <tt>:password</tt> -
|
201
|
-
# * <tt>:database</tt> -
|
65
|
+
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
|
66
|
+
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
|
67
|
+
# * <tt>:database</tt> - Defaults to be the same as the user name.
|
202
68
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
203
69
|
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
204
70
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
205
71
|
# <encoding></tt> call on the connection.
|
206
72
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
207
73
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
74
|
+
# * <tt>:variables</tt> - An optional hash of additional parameters that
|
75
|
+
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
|
76
|
+
# * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
|
77
|
+
# defaults to true.
|
78
|
+
#
|
79
|
+
# Any further options are used as connection parameters to libpq. See
|
80
|
+
# https://www.postgresql.org/docs/current/static/libpq-connect.html for the
|
81
|
+
# list of parameters.
|
82
|
+
#
|
83
|
+
# In addition, default connection parameters of libpq can be set per environment variables.
|
84
|
+
# See https://www.postgresql.org/docs/current/static/libpq-envars.html .
|
208
85
|
class PostgreSQLAdapter < AbstractAdapter
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
86
|
+
ADAPTER_NAME = "PostgreSQL"
|
87
|
+
|
88
|
+
##
|
89
|
+
# :singleton-method:
|
90
|
+
# PostgreSQL allows the creation of "unlogged" tables, which do not record
|
91
|
+
# data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
|
92
|
+
# but significantly increases the risk of data loss if the database
|
93
|
+
# crashes. As a result, this should not be used in production
|
94
|
+
# environments. If you would like all created tables to be unlogged in
|
95
|
+
# the test environment you can add the following line to your test.rb
|
96
|
+
# file:
|
97
|
+
#
|
98
|
+
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
|
99
|
+
class_attribute :create_unlogged_tables, default: false
|
222
100
|
|
223
101
|
NATIVE_DATABASE_TYPES = {
|
224
|
-
:
|
225
|
-
:
|
226
|
-
:
|
227
|
-
:
|
228
|
-
:
|
229
|
-
:
|
230
|
-
:
|
231
|
-
:
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
235
|
-
:
|
236
|
-
:
|
237
|
-
:
|
102
|
+
primary_key: "bigserial primary key",
|
103
|
+
string: { name: "character varying" },
|
104
|
+
text: { name: "text" },
|
105
|
+
integer: { name: "integer", limit: 4 },
|
106
|
+
float: { name: "float" },
|
107
|
+
decimal: { name: "decimal" },
|
108
|
+
datetime: { name: "timestamp" },
|
109
|
+
time: { name: "time" },
|
110
|
+
date: { name: "date" },
|
111
|
+
daterange: { name: "daterange" },
|
112
|
+
numrange: { name: "numrange" },
|
113
|
+
tsrange: { name: "tsrange" },
|
114
|
+
tstzrange: { name: "tstzrange" },
|
115
|
+
int4range: { name: "int4range" },
|
116
|
+
int8range: { name: "int8range" },
|
117
|
+
binary: { name: "bytea" },
|
118
|
+
boolean: { name: "boolean" },
|
119
|
+
xml: { name: "xml" },
|
120
|
+
tsvector: { name: "tsvector" },
|
121
|
+
hstore: { name: "hstore" },
|
122
|
+
inet: { name: "inet" },
|
123
|
+
cidr: { name: "cidr" },
|
124
|
+
macaddr: { name: "macaddr" },
|
125
|
+
uuid: { name: "uuid" },
|
126
|
+
json: { name: "json" },
|
127
|
+
jsonb: { name: "jsonb" },
|
128
|
+
ltree: { name: "ltree" },
|
129
|
+
citext: { name: "citext" },
|
130
|
+
point: { name: "point" },
|
131
|
+
line: { name: "line" },
|
132
|
+
lseg: { name: "lseg" },
|
133
|
+
box: { name: "box" },
|
134
|
+
path: { name: "path" },
|
135
|
+
polygon: { name: "polygon" },
|
136
|
+
circle: { name: "circle" },
|
137
|
+
bit: { name: "bit" },
|
138
|
+
bit_varying: { name: "bit varying" },
|
139
|
+
money: { name: "money" },
|
140
|
+
interval: { name: "interval" },
|
141
|
+
oid: { name: "oid" },
|
238
142
|
}
|
239
143
|
|
240
|
-
|
241
|
-
def adapter_name
|
242
|
-
ADAPTER_NAME
|
243
|
-
end
|
144
|
+
OID = PostgreSQL::OID #:nodoc:
|
244
145
|
|
245
|
-
|
246
|
-
|
247
|
-
|
146
|
+
include PostgreSQL::Quoting
|
147
|
+
include PostgreSQL::ReferentialIntegrity
|
148
|
+
include PostgreSQL::SchemaStatements
|
149
|
+
include PostgreSQL::DatabaseStatements
|
150
|
+
|
151
|
+
def supports_bulk_alter?
|
248
152
|
true
|
249
153
|
end
|
250
154
|
|
@@ -252,929 +156,568 @@ module ActiveRecord
|
|
252
156
|
true
|
253
157
|
end
|
254
158
|
|
255
|
-
|
256
|
-
def initialize(connection, max)
|
257
|
-
super
|
258
|
-
@counter = 0
|
259
|
-
@cache = Hash.new { |h,pid| h[pid] = {} }
|
260
|
-
end
|
261
|
-
|
262
|
-
def each(&block); cache.each(&block); end
|
263
|
-
def key?(key); cache.key?(key); end
|
264
|
-
def [](key); cache[key]; end
|
265
|
-
def length; cache.length; end
|
266
|
-
|
267
|
-
def next_key
|
268
|
-
"a#{@counter + 1}"
|
269
|
-
end
|
270
|
-
|
271
|
-
def []=(sql, key)
|
272
|
-
while @max <= cache.size
|
273
|
-
dealloc(cache.shift.last)
|
274
|
-
end
|
275
|
-
@counter += 1
|
276
|
-
cache[sql] = key
|
277
|
-
end
|
278
|
-
|
279
|
-
def clear
|
280
|
-
cache.each_value do |stmt_key|
|
281
|
-
dealloc stmt_key
|
282
|
-
end
|
283
|
-
cache.clear
|
284
|
-
end
|
285
|
-
|
286
|
-
def delete(sql_key)
|
287
|
-
dealloc cache[sql_key]
|
288
|
-
cache.delete sql_key
|
289
|
-
end
|
290
|
-
|
291
|
-
private
|
292
|
-
def cache
|
293
|
-
@cache[$$]
|
294
|
-
end
|
295
|
-
|
296
|
-
def dealloc(key)
|
297
|
-
@connection.query "DEALLOCATE #{key}" if connection_active?
|
298
|
-
end
|
299
|
-
|
300
|
-
def connection_active?
|
301
|
-
@connection.status == PGconn::CONNECTION_OK
|
302
|
-
rescue PGError
|
303
|
-
false
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
|
308
|
-
include Arel::Visitors::BindVisitor
|
309
|
-
end
|
310
|
-
|
311
|
-
# Initializes and connects a PostgreSQL adapter.
|
312
|
-
def initialize(connection, logger, connection_parameters, config)
|
313
|
-
super(connection, logger)
|
314
|
-
|
315
|
-
if config.fetch(:prepared_statements) { true }
|
316
|
-
@visitor = Arel::Visitors::PostgreSQL.new self
|
317
|
-
else
|
318
|
-
@visitor = BindSubstitution.new self
|
319
|
-
end
|
320
|
-
|
321
|
-
connection_parameters.delete :prepared_statements
|
322
|
-
|
323
|
-
@connection_parameters, @config = connection_parameters, config
|
324
|
-
|
325
|
-
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
326
|
-
@local_tz = nil
|
327
|
-
@table_alias_length = nil
|
328
|
-
|
329
|
-
connect
|
330
|
-
@statements = StatementPool.new @connection,
|
331
|
-
config.fetch(:statement_limit) { 1000 }
|
332
|
-
|
333
|
-
if postgresql_version < 80200
|
334
|
-
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
|
335
|
-
end
|
336
|
-
|
337
|
-
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
338
|
-
end
|
339
|
-
|
340
|
-
# Clears the prepared statements cache.
|
341
|
-
def clear_cache!
|
342
|
-
@statements.clear
|
343
|
-
end
|
344
|
-
|
345
|
-
# Is this connection alive and ready for queries?
|
346
|
-
def active?
|
347
|
-
@connection.query 'SELECT 1'
|
159
|
+
def supports_partial_index?
|
348
160
|
true
|
349
|
-
rescue PGError
|
350
|
-
false
|
351
161
|
end
|
352
162
|
|
353
|
-
|
354
|
-
|
355
|
-
clear_cache!
|
356
|
-
@connection.reset
|
357
|
-
configure_connection
|
358
|
-
end
|
359
|
-
|
360
|
-
def reset!
|
361
|
-
clear_cache!
|
362
|
-
super
|
163
|
+
def supports_expression_index?
|
164
|
+
true
|
363
165
|
end
|
364
166
|
|
365
|
-
|
366
|
-
|
367
|
-
def disconnect!
|
368
|
-
clear_cache!
|
369
|
-
@connection.close rescue nil
|
167
|
+
def supports_transaction_isolation?
|
168
|
+
true
|
370
169
|
end
|
371
170
|
|
372
|
-
def
|
373
|
-
|
171
|
+
def supports_foreign_keys?
|
172
|
+
true
|
374
173
|
end
|
375
174
|
|
376
|
-
|
377
|
-
def supports_migrations?
|
175
|
+
def supports_validate_constraints?
|
378
176
|
true
|
379
177
|
end
|
380
178
|
|
381
|
-
|
382
|
-
def supports_primary_key? #:nodoc:
|
179
|
+
def supports_views?
|
383
180
|
true
|
384
181
|
end
|
385
182
|
|
386
|
-
|
387
|
-
|
388
|
-
old, self.client_min_messages = client_min_messages, 'panic'
|
389
|
-
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
390
|
-
ensure
|
391
|
-
self.client_min_messages = old
|
183
|
+
def supports_datetime_with_precision?
|
184
|
+
true
|
392
185
|
end
|
393
186
|
|
394
|
-
def
|
187
|
+
def supports_json?
|
395
188
|
true
|
396
189
|
end
|
397
190
|
|
398
|
-
def
|
191
|
+
def supports_comments?
|
399
192
|
true
|
400
193
|
end
|
401
194
|
|
402
|
-
# Returns true, since this connection adapter supports savepoints.
|
403
195
|
def supports_savepoints?
|
404
196
|
true
|
405
197
|
end
|
406
198
|
|
407
|
-
|
408
|
-
def supports_explain?
|
199
|
+
def supports_insert_returning?
|
409
200
|
true
|
410
201
|
end
|
411
202
|
|
412
|
-
|
413
|
-
|
414
|
-
@table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
|
415
|
-
end
|
416
|
-
|
417
|
-
# QUOTING ==================================================
|
418
|
-
|
419
|
-
# Escapes binary strings for bytea input to the database.
|
420
|
-
def escape_bytea(value)
|
421
|
-
@connection.escape_bytea(value) if value
|
422
|
-
end
|
423
|
-
|
424
|
-
# Unescapes bytea output from a database to the binary string it represents.
|
425
|
-
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
426
|
-
# on escaped binary output from database drive.
|
427
|
-
def unescape_bytea(value)
|
428
|
-
@connection.unescape_bytea(value) if value
|
429
|
-
end
|
430
|
-
|
431
|
-
# Quotes PostgreSQL-specific data types for SQL input.
|
432
|
-
def quote(value, column = nil) #:nodoc:
|
433
|
-
return super unless column
|
434
|
-
|
435
|
-
case value
|
436
|
-
when Float
|
437
|
-
return super unless value.infinite? && column.type == :datetime
|
438
|
-
"'#{value.to_s.downcase}'"
|
439
|
-
when Numeric
|
440
|
-
return super unless column.sql_type == 'money'
|
441
|
-
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
442
|
-
"'#{value}'"
|
443
|
-
when String
|
444
|
-
case column.sql_type
|
445
|
-
when 'bytea' then "'#{escape_bytea(value)}'"
|
446
|
-
when 'xml' then "xml '#{quote_string(value)}'"
|
447
|
-
when /^bit/
|
448
|
-
case value
|
449
|
-
when /^[01]*$/ then "B'#{value}'" # Bit-string notation
|
450
|
-
when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
|
451
|
-
end
|
452
|
-
else
|
453
|
-
super
|
454
|
-
end
|
455
|
-
else
|
456
|
-
super
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
def type_cast(value, column)
|
461
|
-
return super unless column
|
462
|
-
|
463
|
-
case value
|
464
|
-
when String
|
465
|
-
return super unless 'bytea' == column.sql_type
|
466
|
-
{ :value => value, :format => 1 }
|
467
|
-
else
|
468
|
-
super
|
469
|
-
end
|
203
|
+
def supports_insert_on_conflict?
|
204
|
+
database_version >= 90500
|
470
205
|
end
|
206
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
207
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
208
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
471
209
|
|
472
|
-
|
473
|
-
|
474
|
-
@connection.escape(s)
|
210
|
+
def index_algorithms
|
211
|
+
{ concurrently: "CONCURRENTLY" }
|
475
212
|
end
|
476
213
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
# - schema_name."table.name"
|
483
|
-
# - "schema.name".table_name
|
484
|
-
# - "schema.name"."table.name"
|
485
|
-
def quote_table_name(name)
|
486
|
-
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
487
|
-
|
488
|
-
unless name_part
|
489
|
-
quote_column_name(schema)
|
490
|
-
else
|
491
|
-
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
492
|
-
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
214
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
215
|
+
def initialize(connection, max)
|
216
|
+
super(max)
|
217
|
+
@connection = connection
|
218
|
+
@counter = 0
|
493
219
|
end
|
494
|
-
end
|
495
220
|
|
496
|
-
|
497
|
-
|
498
|
-
PGconn.quote_ident(name.to_s)
|
499
|
-
end
|
500
|
-
|
501
|
-
# Quote date/time values for use in SQL input. Includes microseconds
|
502
|
-
# if the value is a Time responding to usec.
|
503
|
-
def quoted_date(value) #:nodoc:
|
504
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
505
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
506
|
-
else
|
507
|
-
super
|
221
|
+
def next_key
|
222
|
+
"a#{@counter + 1}"
|
508
223
|
end
|
509
|
-
end
|
510
|
-
|
511
|
-
# Set the authorized user for this session
|
512
|
-
def session_auth=(user)
|
513
|
-
clear_cache!
|
514
|
-
exec_query "SET SESSION AUTHORIZATION #{user}"
|
515
|
-
end
|
516
224
|
|
517
|
-
|
518
|
-
|
519
|
-
def supports_disable_referential_integrity? #:nodoc:
|
520
|
-
true
|
521
|
-
end
|
522
|
-
|
523
|
-
def disable_referential_integrity #:nodoc:
|
524
|
-
if supports_disable_referential_integrity? then
|
525
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
526
|
-
end
|
527
|
-
yield
|
528
|
-
ensure
|
529
|
-
if supports_disable_referential_integrity? then
|
530
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
225
|
+
def []=(sql, key)
|
226
|
+
super.tap { @counter += 1 }
|
531
227
|
end
|
532
|
-
end
|
533
228
|
|
534
|
-
|
229
|
+
private
|
230
|
+
def dealloc(key)
|
231
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
232
|
+
rescue PG::Error
|
233
|
+
end
|
535
234
|
|
536
|
-
|
537
|
-
|
538
|
-
|
235
|
+
def connection_active?
|
236
|
+
@connection.status == PG::CONNECTION_OK
|
237
|
+
rescue PG::Error
|
238
|
+
false
|
239
|
+
end
|
539
240
|
end
|
540
241
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
#
|
545
|
-
# QUERY PLAN
|
546
|
-
# ------------------------------------------------------------------------------
|
547
|
-
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
548
|
-
# Join Filter: (posts.user_id = users.id)
|
549
|
-
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
550
|
-
# Index Cond: (id = 1)
|
551
|
-
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
552
|
-
# Filter: (posts.user_id = 1)
|
553
|
-
# (6 rows)
|
554
|
-
#
|
555
|
-
def pp(result)
|
556
|
-
header = result.columns.first
|
557
|
-
lines = result.rows.map(&:first)
|
558
|
-
|
559
|
-
# We add 2 because there's one char of padding at both sides, note
|
560
|
-
# the extra hyphens in the example above.
|
561
|
-
width = [header, *lines].map(&:length).max + 2
|
562
|
-
|
563
|
-
pp = []
|
242
|
+
# Initializes and connects a PostgreSQL adapter.
|
243
|
+
def initialize(connection, logger, connection_parameters, config)
|
244
|
+
super(connection, logger, config)
|
564
245
|
|
565
|
-
|
566
|
-
pp << '-' * width
|
246
|
+
@connection_parameters = connection_parameters
|
567
247
|
|
568
|
-
|
248
|
+
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
249
|
+
@local_tz = nil
|
250
|
+
@max_identifier_length = nil
|
569
251
|
|
570
|
-
|
571
|
-
|
572
|
-
|
252
|
+
configure_connection
|
253
|
+
add_pg_encoders
|
254
|
+
add_pg_decoders
|
573
255
|
|
574
|
-
|
575
|
-
|
256
|
+
@type_map = Type::HashLookupTypeMap.new
|
257
|
+
initialize_type_map
|
258
|
+
@local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
|
259
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
576
260
|
end
|
577
261
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
262
|
+
def self.database_exists?(config)
|
263
|
+
!!ActiveRecord::Base.postgresql_connection(config)
|
264
|
+
rescue ActiveRecord::NoDatabaseError
|
265
|
+
false
|
582
266
|
end
|
583
267
|
|
584
|
-
#
|
585
|
-
def
|
586
|
-
|
587
|
-
|
588
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
589
|
-
pk = primary_key(table_ref) if table_ref
|
268
|
+
# Is this connection alive and ready for queries?
|
269
|
+
def active?
|
270
|
+
@lock.synchronize do
|
271
|
+
@connection.query "SELECT 1"
|
590
272
|
end
|
273
|
+
true
|
274
|
+
rescue PG::Error
|
275
|
+
false
|
276
|
+
end
|
591
277
|
|
592
|
-
|
593
|
-
|
594
|
-
|
278
|
+
# Close then reopen the connection.
|
279
|
+
def reconnect!
|
280
|
+
@lock.synchronize do
|
595
281
|
super
|
282
|
+
@connection.reset
|
283
|
+
configure_connection
|
284
|
+
rescue PG::ConnectionBad
|
285
|
+
connect
|
596
286
|
end
|
597
287
|
end
|
598
|
-
alias :create :insert
|
599
|
-
|
600
|
-
# create a 2D array representing the result set
|
601
|
-
def result_as_array(res) #:nodoc:
|
602
|
-
# check if we have any binary column and if they need escaping
|
603
|
-
ftypes = Array.new(res.nfields) do |i|
|
604
|
-
[i, res.ftype(i)]
|
605
|
-
end
|
606
|
-
|
607
|
-
rows = res.values
|
608
|
-
return rows unless ftypes.any? { |_, x|
|
609
|
-
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
|
610
|
-
}
|
611
|
-
|
612
|
-
typehash = ftypes.group_by { |_, type| type }
|
613
|
-
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
614
|
-
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
615
288
|
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
# If this is a money type column and there are any currency symbols,
|
623
|
-
# then strip them off. Indeed it would be prettier to do this in
|
624
|
-
# PostgreSQLColumn.string_to_decimal but would break form input
|
625
|
-
# fields that call value_before_type_cast.
|
626
|
-
monies.each do |index, _|
|
627
|
-
data = row[index]
|
628
|
-
# Because money output is formatted according to the locale, there are two
|
629
|
-
# cases to consider (note the decimal separators):
|
630
|
-
# (1) $12,345,678.12
|
631
|
-
# (2) $12.345.678,12
|
632
|
-
case data
|
633
|
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
634
|
-
data.gsub!(/[^-\d.]/, '')
|
635
|
-
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
636
|
-
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
637
|
-
end
|
289
|
+
def reset!
|
290
|
+
@lock.synchronize do
|
291
|
+
clear_cache!
|
292
|
+
reset_transaction
|
293
|
+
unless @connection.transaction_status == ::PG::PQTRANS_IDLE
|
294
|
+
@connection.query "ROLLBACK"
|
638
295
|
end
|
296
|
+
@connection.query "DISCARD ALL"
|
297
|
+
configure_connection
|
639
298
|
end
|
640
299
|
end
|
641
300
|
|
642
|
-
|
643
|
-
#
|
644
|
-
def
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
end
|
649
|
-
|
650
|
-
# Executes an SQL statement, returning a PGresult object on success
|
651
|
-
# or raising a PGError exception otherwise.
|
652
|
-
def execute(sql, name = nil)
|
653
|
-
log(sql, name) do
|
654
|
-
@connection.async_exec(sql)
|
655
|
-
end
|
656
|
-
end
|
657
|
-
|
658
|
-
def substitute_at(column, index)
|
659
|
-
Arel::Nodes::BindParam.new "$#{index + 1}"
|
660
|
-
end
|
661
|
-
|
662
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
663
|
-
log(sql, name, binds) do
|
664
|
-
result = binds.empty? ? exec_no_cache(sql, binds) :
|
665
|
-
exec_cache(sql, binds)
|
666
|
-
|
667
|
-
ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
|
668
|
-
result.clear
|
669
|
-
return ret
|
301
|
+
# Disconnects from the database if already connected. Otherwise, this
|
302
|
+
# method does nothing.
|
303
|
+
def disconnect!
|
304
|
+
@lock.synchronize do
|
305
|
+
super
|
306
|
+
@connection.close rescue nil
|
670
307
|
end
|
671
308
|
end
|
672
309
|
|
673
|
-
def
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
affected = result.cmd_tuples
|
678
|
-
result.clear
|
679
|
-
affected
|
680
|
-
end
|
310
|
+
def discard! # :nodoc:
|
311
|
+
super
|
312
|
+
@connection.socket_io.reopen(IO::NULL) rescue nil
|
313
|
+
@connection = nil
|
681
314
|
end
|
682
|
-
alias :exec_update :exec_delete
|
683
315
|
|
684
|
-
def
|
685
|
-
|
686
|
-
# Extract the table from the insert sql. Yuck.
|
687
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
688
|
-
pk = primary_key(table_ref) if table_ref
|
689
|
-
end
|
690
|
-
|
691
|
-
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
|
692
|
-
|
693
|
-
[sql, binds]
|
316
|
+
def native_database_types #:nodoc:
|
317
|
+
NATIVE_DATABASE_TYPES
|
694
318
|
end
|
695
319
|
|
696
|
-
|
697
|
-
|
698
|
-
super.cmd_tuples
|
320
|
+
def set_standard_conforming_strings
|
321
|
+
execute("SET standard_conforming_strings = on", "SCHEMA")
|
699
322
|
end
|
700
323
|
|
701
|
-
|
702
|
-
|
703
|
-
execute "BEGIN"
|
324
|
+
def supports_ddl_transactions?
|
325
|
+
true
|
704
326
|
end
|
705
327
|
|
706
|
-
|
707
|
-
|
708
|
-
execute "COMMIT"
|
328
|
+
def supports_advisory_locks?
|
329
|
+
true
|
709
330
|
end
|
710
331
|
|
711
|
-
|
712
|
-
|
713
|
-
execute "ROLLBACK"
|
332
|
+
def supports_explain?
|
333
|
+
true
|
714
334
|
end
|
715
335
|
|
716
|
-
def
|
717
|
-
|
336
|
+
def supports_extensions?
|
337
|
+
true
|
718
338
|
end
|
719
339
|
|
720
|
-
def
|
721
|
-
|
340
|
+
def supports_ranges?
|
341
|
+
true
|
722
342
|
end
|
343
|
+
deprecate :supports_ranges?
|
723
344
|
|
724
|
-
def
|
725
|
-
|
345
|
+
def supports_materialized_views?
|
346
|
+
true
|
726
347
|
end
|
727
348
|
|
728
|
-
def
|
729
|
-
|
349
|
+
def supports_foreign_tables?
|
350
|
+
true
|
730
351
|
end
|
731
352
|
|
732
|
-
|
733
|
-
|
734
|
-
# Drops the database specified on the +name+ attribute
|
735
|
-
# and creates it again using the provided +options+.
|
736
|
-
def recreate_database(name, options = {}) #:nodoc:
|
737
|
-
drop_database(name)
|
738
|
-
create_database(name, options)
|
353
|
+
def supports_pgcrypto_uuid?
|
354
|
+
database_version >= 90400
|
739
355
|
end
|
740
356
|
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
#
|
745
|
-
# Example:
|
746
|
-
# create_database config[:database], config
|
747
|
-
# create_database 'foo_development', :encoding => 'unicode'
|
748
|
-
def create_database(name, options = {})
|
749
|
-
options = options.reverse_merge(:encoding => "utf8")
|
750
|
-
|
751
|
-
option_string = options.symbolize_keys.sum do |key, value|
|
752
|
-
case key
|
753
|
-
when :owner
|
754
|
-
" OWNER = \"#{value}\""
|
755
|
-
when :template
|
756
|
-
" TEMPLATE = \"#{value}\""
|
757
|
-
when :encoding
|
758
|
-
" ENCODING = '#{value}'"
|
759
|
-
when :tablespace
|
760
|
-
" TABLESPACE = \"#{value}\""
|
761
|
-
when :connection_limit
|
762
|
-
" CONNECTION LIMIT = #{value}"
|
763
|
-
else
|
764
|
-
""
|
765
|
-
end
|
357
|
+
def supports_optimizer_hints?
|
358
|
+
unless defined?(@has_pg_hint_plan)
|
359
|
+
@has_pg_hint_plan = extension_available?("pg_hint_plan")
|
766
360
|
end
|
767
|
-
|
768
|
-
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
|
361
|
+
@has_pg_hint_plan
|
769
362
|
end
|
770
363
|
|
771
|
-
|
772
|
-
|
773
|
-
# Example:
|
774
|
-
# drop_database 'matt_development'
|
775
|
-
def drop_database(name) #:nodoc:
|
776
|
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
364
|
+
def supports_lazy_transactions?
|
365
|
+
true
|
777
366
|
end
|
778
367
|
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
WHERE schemaname = ANY (current_schemas(false))
|
785
|
-
SQL
|
368
|
+
def get_advisory_lock(lock_id) # :nodoc:
|
369
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
370
|
+
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
|
371
|
+
end
|
372
|
+
query_value("SELECT pg_try_advisory_lock(#{lock_id})")
|
786
373
|
end
|
787
374
|
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
return false unless table
|
794
|
-
|
795
|
-
binds = [[nil, table]]
|
796
|
-
binds << [nil, schema] if schema
|
797
|
-
|
798
|
-
exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
|
799
|
-
SELECT COUNT(*)
|
800
|
-
FROM pg_class c
|
801
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
802
|
-
WHERE c.relkind in ('v','r')
|
803
|
-
AND c.relname = $1
|
804
|
-
AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
|
805
|
-
SQL
|
375
|
+
def release_advisory_lock(lock_id) # :nodoc:
|
376
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
377
|
+
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
|
378
|
+
end
|
379
|
+
query_value("SELECT pg_advisory_unlock(#{lock_id})")
|
806
380
|
end
|
807
381
|
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
FROM pg_namespace
|
813
|
-
WHERE nspname = $1
|
814
|
-
SQL
|
382
|
+
def enable_extension(name)
|
383
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
384
|
+
reload_type_map
|
385
|
+
}
|
815
386
|
end
|
816
387
|
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
FROM pg_class t
|
822
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
823
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
824
|
-
WHERE i.relkind = 'i'
|
825
|
-
AND d.indisprimary = 'f'
|
826
|
-
AND t.relname = '#{table_name}'
|
827
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
828
|
-
ORDER BY i.relname
|
829
|
-
SQL
|
830
|
-
|
831
|
-
|
832
|
-
result.map do |row|
|
833
|
-
index_name = row[0]
|
834
|
-
unique = row[1] == 't'
|
835
|
-
indkey = row[2].split(" ")
|
836
|
-
inddef = row[3]
|
837
|
-
oid = row[4]
|
838
|
-
|
839
|
-
columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
|
840
|
-
SELECT a.attnum, a.attname
|
841
|
-
FROM pg_attribute a
|
842
|
-
WHERE a.attrelid = #{oid}
|
843
|
-
AND a.attnum IN (#{indkey.join(",")})
|
844
|
-
SQL
|
845
|
-
|
846
|
-
column_names = columns.values_at(*indkey).compact
|
847
|
-
|
848
|
-
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
849
|
-
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
850
|
-
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
851
|
-
|
852
|
-
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
853
|
-
end.compact
|
388
|
+
def disable_extension(name)
|
389
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
390
|
+
reload_type_map
|
391
|
+
}
|
854
392
|
end
|
855
393
|
|
856
|
-
|
857
|
-
|
858
|
-
# Limit, precision, and scale are all handled by the superclass.
|
859
|
-
column_definitions(table_name).collect do |column_name, type, default, notnull|
|
860
|
-
PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
|
861
|
-
end
|
394
|
+
def extension_available?(name)
|
395
|
+
query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
862
396
|
end
|
863
397
|
|
864
|
-
|
865
|
-
|
866
|
-
query('select current_database()')[0][0]
|
398
|
+
def extension_enabled?(name)
|
399
|
+
query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
867
400
|
end
|
868
401
|
|
869
|
-
|
870
|
-
|
871
|
-
query('SELECT current_schema', 'SCHEMA')[0][0]
|
402
|
+
def extensions
|
403
|
+
exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
|
872
404
|
end
|
873
405
|
|
874
|
-
# Returns the
|
875
|
-
def
|
876
|
-
|
877
|
-
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
878
|
-
WHERE pg_database.datname LIKE '#{current_database}'
|
879
|
-
end_sql
|
406
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
407
|
+
def max_identifier_length
|
408
|
+
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
|
880
409
|
end
|
881
410
|
|
882
|
-
#
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
# This should be not be called manually but set in database.yml.
|
887
|
-
def schema_search_path=(schema_csv)
|
888
|
-
if schema_csv
|
889
|
-
execute("SET search_path TO #{schema_csv}", 'SCHEMA')
|
890
|
-
@schema_search_path = schema_csv
|
891
|
-
end
|
411
|
+
# Set the authorized user for this session
|
412
|
+
def session_auth=(user)
|
413
|
+
clear_cache!
|
414
|
+
execute("SET SESSION AUTHORIZATION #{user}")
|
892
415
|
end
|
893
416
|
|
894
|
-
|
895
|
-
|
896
|
-
@schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
|
417
|
+
def use_insert_returning?
|
418
|
+
@use_insert_returning
|
897
419
|
end
|
898
420
|
|
899
|
-
|
900
|
-
|
901
|
-
query('SHOW client_min_messages', 'SCHEMA')[0][0]
|
421
|
+
def column_name_for_operation(operation, node) # :nodoc:
|
422
|
+
OPERATION_ALIASES.fetch(operation) { operation.downcase }
|
902
423
|
end
|
903
424
|
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
425
|
+
OPERATION_ALIASES = { # :nodoc:
|
426
|
+
"maximum" => "max",
|
427
|
+
"minimum" => "min",
|
428
|
+
"average" => "avg",
|
429
|
+
}
|
908
430
|
|
909
|
-
# Returns the
|
910
|
-
def
|
911
|
-
|
912
|
-
rescue ActiveRecord::StatementInvalid
|
913
|
-
"#{table_name}_#{pk || 'id'}_seq"
|
431
|
+
# Returns the version of the connected PostgreSQL server.
|
432
|
+
def get_database_version # :nodoc:
|
433
|
+
@connection.server_version
|
914
434
|
end
|
435
|
+
alias :postgresql_version :database_version
|
915
436
|
|
916
|
-
def
|
917
|
-
|
918
|
-
SELECT pg_get_serial_sequence($1, $2)
|
919
|
-
eosql
|
920
|
-
result.rows.first.first
|
437
|
+
def default_index_type?(index) # :nodoc:
|
438
|
+
index.using == :btree || super
|
921
439
|
end
|
922
440
|
|
923
|
-
|
924
|
-
|
925
|
-
unless pk and sequence
|
926
|
-
default_pk, default_sequence = pk_and_sequence_for(table)
|
927
|
-
|
928
|
-
pk ||= default_pk
|
929
|
-
sequence ||= default_sequence
|
930
|
-
end
|
441
|
+
def build_insert_sql(insert) # :nodoc:
|
442
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
931
443
|
|
932
|
-
if
|
933
|
-
|
444
|
+
if insert.skip_duplicates?
|
445
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
446
|
+
elsif insert.update_duplicates?
|
447
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
448
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
934
449
|
end
|
935
450
|
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
select_value <<-end_sql, 'Reset sequence'
|
940
|
-
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)
|
941
|
-
end_sql
|
942
|
-
end
|
451
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
452
|
+
sql
|
943
453
|
end
|
944
454
|
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
# given table's primary key.
|
949
|
-
result = query(<<-end_sql, 'PK and serial sequence')[0]
|
950
|
-
SELECT attr.attname, seq.relname
|
951
|
-
FROM pg_class seq,
|
952
|
-
pg_attribute attr,
|
953
|
-
pg_depend dep,
|
954
|
-
pg_namespace name,
|
955
|
-
pg_constraint cons
|
956
|
-
WHERE seq.oid = dep.objid
|
957
|
-
AND seq.relkind = 'S'
|
958
|
-
AND attr.attrelid = dep.refobjid
|
959
|
-
AND attr.attnum = dep.refobjsubid
|
960
|
-
AND attr.attrelid = cons.conrelid
|
961
|
-
AND attr.attnum = cons.conkey[1]
|
962
|
-
AND cons.contype = 'p'
|
963
|
-
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
964
|
-
end_sql
|
965
|
-
|
966
|
-
if result.nil? or result.empty?
|
967
|
-
# If that fails, try parsing the primary key's default value.
|
968
|
-
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
969
|
-
# the 8.1+ nextval('foo'::regclass).
|
970
|
-
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
971
|
-
SELECT attr.attname,
|
972
|
-
CASE
|
973
|
-
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
974
|
-
substr(split_part(def.adsrc, '''', 2),
|
975
|
-
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
976
|
-
ELSE split_part(def.adsrc, '''', 2)
|
977
|
-
END
|
978
|
-
FROM pg_class t
|
979
|
-
JOIN pg_attribute attr ON (t.oid = attrelid)
|
980
|
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
981
|
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
982
|
-
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
983
|
-
AND cons.contype = 'p'
|
984
|
-
AND def.adsrc ~* 'nextval'
|
985
|
-
end_sql
|
455
|
+
def check_version # :nodoc:
|
456
|
+
if database_version < 90300
|
457
|
+
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
986
458
|
end
|
987
|
-
|
988
|
-
[result.first, result.last]
|
989
|
-
rescue
|
990
|
-
nil
|
991
459
|
end
|
992
460
|
|
993
|
-
|
994
|
-
def primary_key(table)
|
995
|
-
row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
|
996
|
-
SELECT DISTINCT(attr.attname)
|
997
|
-
FROM pg_attribute attr
|
998
|
-
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
|
999
|
-
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
|
1000
|
-
WHERE cons.contype = 'p'
|
1001
|
-
AND dep.refobjid = $1::regclass
|
1002
|
-
end_sql
|
1003
|
-
|
1004
|
-
row && row.first
|
1005
|
-
end
|
461
|
+
private
|
1006
462
|
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
463
|
+
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
464
|
+
VALUE_LIMIT_VIOLATION = "22001"
|
465
|
+
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
|
466
|
+
NOT_NULL_VIOLATION = "23502"
|
467
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
468
|
+
UNIQUE_VIOLATION = "23505"
|
469
|
+
SERIALIZATION_FAILURE = "40001"
|
470
|
+
DEADLOCK_DETECTED = "40P01"
|
471
|
+
LOCK_NOT_AVAILABLE = "55P03"
|
472
|
+
QUERY_CANCELED = "57014"
|
473
|
+
|
474
|
+
def translate_exception(exception, message:, sql:, binds:)
|
475
|
+
return exception unless exception.respond_to?(:result)
|
476
|
+
|
477
|
+
case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
|
478
|
+
when UNIQUE_VIOLATION
|
479
|
+
RecordNotUnique.new(message, sql: sql, binds: binds)
|
480
|
+
when FOREIGN_KEY_VIOLATION
|
481
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
482
|
+
when VALUE_LIMIT_VIOLATION
|
483
|
+
ValueTooLong.new(message, sql: sql, binds: binds)
|
484
|
+
when NUMERIC_VALUE_OUT_OF_RANGE
|
485
|
+
RangeError.new(message, sql: sql, binds: binds)
|
486
|
+
when NOT_NULL_VIOLATION
|
487
|
+
NotNullViolation.new(message, sql: sql, binds: binds)
|
488
|
+
when SERIALIZATION_FAILURE
|
489
|
+
SerializationFailure.new(message, sql: sql, binds: binds)
|
490
|
+
when DEADLOCK_DETECTED
|
491
|
+
Deadlocked.new(message, sql: sql, binds: binds)
|
492
|
+
when LOCK_NOT_AVAILABLE
|
493
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds)
|
494
|
+
when QUERY_CANCELED
|
495
|
+
QueryCanceled.new(message, sql: sql, binds: binds)
|
496
|
+
else
|
497
|
+
super
|
498
|
+
end
|
499
|
+
end
|
1015
500
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
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])}"
|
1021
|
-
add_column_options!(add_column_sql, options)
|
501
|
+
def get_oid_type(oid, fmod, column_name, sql_type = "")
|
502
|
+
if !type_map.key?(oid)
|
503
|
+
load_additional_types([oid])
|
504
|
+
end
|
1022
505
|
|
1023
|
-
|
1024
|
-
|
506
|
+
type_map.fetch(oid, fmod, sql_type) {
|
507
|
+
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
|
508
|
+
Type.default_value.tap do |cast_type|
|
509
|
+
type_map.register_type(oid, cast_type)
|
510
|
+
end
|
511
|
+
}
|
512
|
+
end
|
513
|
+
|
514
|
+
def initialize_type_map(m = type_map)
|
515
|
+
m.register_type "int2", Type::Integer.new(limit: 2)
|
516
|
+
m.register_type "int4", Type::Integer.new(limit: 4)
|
517
|
+
m.register_type "int8", Type::Integer.new(limit: 8)
|
518
|
+
m.register_type "oid", OID::Oid.new
|
519
|
+
m.register_type "float4", Type::Float.new
|
520
|
+
m.alias_type "float8", "float4"
|
521
|
+
m.register_type "text", Type::Text.new
|
522
|
+
register_class_with_limit m, "varchar", Type::String
|
523
|
+
m.alias_type "char", "varchar"
|
524
|
+
m.alias_type "name", "varchar"
|
525
|
+
m.alias_type "bpchar", "varchar"
|
526
|
+
m.register_type "bool", Type::Boolean.new
|
527
|
+
register_class_with_limit m, "bit", OID::Bit
|
528
|
+
register_class_with_limit m, "varbit", OID::BitVarying
|
529
|
+
m.alias_type "timestamptz", "timestamp"
|
530
|
+
m.register_type "date", OID::Date.new
|
531
|
+
|
532
|
+
m.register_type "money", OID::Money.new
|
533
|
+
m.register_type "bytea", OID::Bytea.new
|
534
|
+
m.register_type "point", OID::Point.new
|
535
|
+
m.register_type "hstore", OID::Hstore.new
|
536
|
+
m.register_type "json", Type::Json.new
|
537
|
+
m.register_type "jsonb", OID::Jsonb.new
|
538
|
+
m.register_type "cidr", OID::Cidr.new
|
539
|
+
m.register_type "inet", OID::Inet.new
|
540
|
+
m.register_type "uuid", OID::Uuid.new
|
541
|
+
m.register_type "xml", OID::Xml.new
|
542
|
+
m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
|
543
|
+
m.register_type "macaddr", OID::SpecializedString.new(:macaddr)
|
544
|
+
m.register_type "citext", OID::SpecializedString.new(:citext)
|
545
|
+
m.register_type "ltree", OID::SpecializedString.new(:ltree)
|
546
|
+
m.register_type "line", OID::SpecializedString.new(:line)
|
547
|
+
m.register_type "lseg", OID::SpecializedString.new(:lseg)
|
548
|
+
m.register_type "box", OID::SpecializedString.new(:box)
|
549
|
+
m.register_type "path", OID::SpecializedString.new(:path)
|
550
|
+
m.register_type "polygon", OID::SpecializedString.new(:polygon)
|
551
|
+
m.register_type "circle", OID::SpecializedString.new(:circle)
|
552
|
+
|
553
|
+
m.register_type "interval" do |_, _, sql_type|
|
554
|
+
precision = extract_precision(sql_type)
|
555
|
+
OID::SpecializedString.new(:interval, precision: precision)
|
556
|
+
end
|
1025
557
|
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
558
|
+
register_class_with_precision m, "time", Type::Time
|
559
|
+
register_class_with_precision m, "timestamp", OID::DateTime
|
560
|
+
|
561
|
+
m.register_type "numeric" do |_, fmod, sql_type|
|
562
|
+
precision = extract_precision(sql_type)
|
563
|
+
scale = extract_scale(sql_type)
|
564
|
+
|
565
|
+
# The type for the numeric depends on the width of the field,
|
566
|
+
# so we'll do something special here.
|
567
|
+
#
|
568
|
+
# When dealing with decimal columns:
|
569
|
+
#
|
570
|
+
# places after decimal = fmod - 4 & 0xffff
|
571
|
+
# places before decimal = (fmod - 4) >> 16 & 0xffff
|
572
|
+
if fmod && (fmod - 4 & 0xffff).zero?
|
573
|
+
# FIXME: Remove this class, and the second argument to
|
574
|
+
# lookups on PG
|
575
|
+
Type::DecimalWithoutScale.new(precision: precision)
|
576
|
+
else
|
577
|
+
OID::Decimal.new(precision: precision, scale: scale)
|
578
|
+
end
|
579
|
+
end
|
1030
580
|
|
1031
|
-
|
581
|
+
load_additional_types
|
582
|
+
end
|
1032
583
|
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
584
|
+
# Extracts the value from a PostgreSQL column default definition.
|
585
|
+
def extract_value_from_default(default)
|
586
|
+
case default
|
587
|
+
# Quoted types
|
588
|
+
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
|
589
|
+
# The default 'now'::date is CURRENT_DATE
|
590
|
+
if $1 == "now" && $2 == "date"
|
591
|
+
nil
|
592
|
+
else
|
593
|
+
$1.gsub("''", "'")
|
594
|
+
end
|
595
|
+
# Boolean types
|
596
|
+
when "true", "false"
|
597
|
+
default
|
598
|
+
# Numeric types
|
599
|
+
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
|
600
|
+
$1
|
601
|
+
# Object identifier types
|
602
|
+
when /\A-?\d+\z/
|
603
|
+
$1
|
604
|
+
else
|
605
|
+
# Anything else is blank, some user type, or some function
|
606
|
+
# and we can't know the value of that, so return nil.
|
607
|
+
nil
|
608
|
+
end
|
609
|
+
end
|
1036
610
|
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
1041
|
-
end
|
611
|
+
def extract_default_function(default_value, default)
|
612
|
+
default if has_default_function?(default_value, default)
|
613
|
+
end
|
1042
614
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
unless null || default.nil?
|
1046
|
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
615
|
+
def has_default_function?(default_value, default)
|
616
|
+
!default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
|
1047
617
|
end
|
1048
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
1049
|
-
end
|
1050
618
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
clear_cache!
|
1054
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
1055
|
-
end
|
619
|
+
def load_additional_types(oids = nil)
|
620
|
+
initializer = OID::TypeMapInitializer.new(type_map)
|
1056
621
|
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
622
|
+
query = <<~SQL
|
623
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
624
|
+
FROM pg_type as t
|
625
|
+
LEFT JOIN pg_range as r ON oid = rngtypid
|
626
|
+
SQL
|
1060
627
|
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
628
|
+
if oids
|
629
|
+
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
|
630
|
+
else
|
631
|
+
query += initializer.query_conditions_for_initial_load
|
632
|
+
end
|
1064
633
|
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
634
|
+
execute_and_clear(query, "SCHEMA", []) do |records|
|
635
|
+
initializer.run(records)
|
636
|
+
end
|
637
|
+
end
|
1068
638
|
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
|
1075
|
-
case limit
|
1076
|
-
when nil, 0..0x3fffffff; super(type)
|
1077
|
-
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
639
|
+
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
640
|
+
|
641
|
+
def execute_and_clear(sql, name, binds, prepare: false)
|
642
|
+
if preventing_writes? && write_query?(sql)
|
643
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
1078
644
|
end
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
645
|
+
|
646
|
+
if without_prepared_statement?(binds)
|
647
|
+
result = exec_no_cache(sql, name, [])
|
648
|
+
elsif !prepare
|
649
|
+
result = exec_no_cache(sql, name, binds)
|
650
|
+
else
|
651
|
+
result = exec_cache(sql, name, binds)
|
1087
652
|
end
|
1088
|
-
|
1089
|
-
|
653
|
+
ret = yield result
|
654
|
+
result.clear
|
655
|
+
ret
|
1090
656
|
end
|
1091
|
-
end
|
1092
657
|
|
1093
|
-
|
1094
|
-
|
1095
|
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
1096
|
-
# requires that the ORDER BY include the distinct column.
|
1097
|
-
#
|
1098
|
-
# distinct("posts.id", "posts.created_at desc")
|
1099
|
-
def distinct(columns, orders) #:nodoc:
|
1100
|
-
return "DISTINCT #{columns}" if orders.empty?
|
658
|
+
def exec_no_cache(sql, name, binds)
|
659
|
+
materialize_transactions
|
1101
660
|
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
order_columns.delete_if { |c| c.blank? }
|
1106
|
-
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
661
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
662
|
+
# made since we established the connection
|
663
|
+
update_typemap_for_default_timezone
|
1107
664
|
|
1108
|
-
|
1109
|
-
|
665
|
+
type_casted_binds = type_casted_binds(binds)
|
666
|
+
log(sql, name, binds, type_casted_binds) do
|
667
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
668
|
+
@connection.exec_params(sql, type_casted_binds)
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
1110
672
|
|
1111
|
-
|
1112
|
-
|
673
|
+
def exec_cache(sql, name, binds)
|
674
|
+
materialize_transactions
|
675
|
+
update_typemap_for_default_timezone
|
1113
676
|
|
1114
|
-
|
1115
|
-
|
1116
|
-
# +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
|
1117
|
-
# +name+ supports the range of schema/table references understood by PostgreSQL, for example:
|
1118
|
-
#
|
1119
|
-
# * <tt>table_name</tt>
|
1120
|
-
# * <tt>"table.name"</tt>
|
1121
|
-
# * <tt>schema_name.table_name</tt>
|
1122
|
-
# * <tt>schema_name."table.name"</tt>
|
1123
|
-
# * <tt>"schema.name"."table name"</tt>
|
1124
|
-
def extract_schema_and_table(name)
|
1125
|
-
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
|
1126
|
-
[schema, table]
|
1127
|
-
end
|
1128
|
-
end
|
677
|
+
stmt_key = prepare_statement(sql, binds)
|
678
|
+
type_casted_binds = type_casted_binds(binds)
|
1129
679
|
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
680
|
+
log(sql, name, binds, type_casted_binds, stmt_key) do
|
681
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
682
|
+
@connection.exec_prepared(stmt_key, type_casted_binds)
|
683
|
+
end
|
684
|
+
end
|
685
|
+
rescue ActiveRecord::StatementInvalid => e
|
686
|
+
raise unless is_cached_plan_failure?(e)
|
1135
687
|
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
when /violates foreign key constraint/
|
1141
|
-
InvalidForeignKey.new(message, exception)
|
688
|
+
# Nothing we can do if we are in a transaction because all commands
|
689
|
+
# will raise InFailedSQLTransaction
|
690
|
+
if in_transaction?
|
691
|
+
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
|
1142
692
|
else
|
1143
|
-
|
693
|
+
@lock.synchronize do
|
694
|
+
# outside of transactions we can simply flush this query and retry
|
695
|
+
@statements.delete sql_key(sql)
|
696
|
+
end
|
697
|
+
retry
|
1144
698
|
end
|
1145
699
|
end
|
1146
700
|
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
701
|
+
# Annoyingly, the code for prepared statements whose return value may
|
702
|
+
# have changed is FEATURE_NOT_SUPPORTED.
|
703
|
+
#
|
704
|
+
# This covers various different error types so we need to do additional
|
705
|
+
# work to classify the exception definitively as a
|
706
|
+
# ActiveRecord::PreparedStatementCacheExpired
|
707
|
+
#
|
708
|
+
# Check here for more details:
|
709
|
+
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
710
|
+
CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
|
711
|
+
def is_cached_plan_failure?(e)
|
712
|
+
pgerror = e.cause
|
713
|
+
code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
|
714
|
+
code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
|
715
|
+
rescue
|
716
|
+
false
|
1152
717
|
end
|
1153
718
|
|
1154
|
-
def
|
1155
|
-
|
1156
|
-
stmt_key = prepare_statement sql
|
1157
|
-
|
1158
|
-
# Clear the queue
|
1159
|
-
@connection.get_last_result
|
1160
|
-
@connection.send_query_prepared(stmt_key, binds.map { |col, val|
|
1161
|
-
type_cast(val, col)
|
1162
|
-
})
|
1163
|
-
@connection.block
|
1164
|
-
@connection.get_last_result
|
1165
|
-
rescue PGError => e
|
1166
|
-
# Get the PG code for the failure. Annoyingly, the code for
|
1167
|
-
# prepared statements whose return value may have changed is
|
1168
|
-
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
1169
|
-
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1170
|
-
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
1171
|
-
if FEATURE_NOT_SUPPORTED == code
|
1172
|
-
@statements.delete sql_key(sql)
|
1173
|
-
retry
|
1174
|
-
else
|
1175
|
-
raise e
|
1176
|
-
end
|
1177
|
-
end
|
719
|
+
def in_transaction?
|
720
|
+
open_transactions > 0
|
1178
721
|
end
|
1179
722
|
|
1180
723
|
# Returns the statement identifier for the client side cache
|
@@ -1185,32 +728,31 @@ module ActiveRecord
|
|
1185
728
|
|
1186
729
|
# Prepare the statement if it hasn't been prepared, return
|
1187
730
|
# the statement key.
|
1188
|
-
def prepare_statement(sql)
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
731
|
+
def prepare_statement(sql, binds)
|
732
|
+
@lock.synchronize do
|
733
|
+
sql_key = sql_key(sql)
|
734
|
+
unless @statements.key? sql_key
|
735
|
+
nextkey = @statements.next_key
|
736
|
+
begin
|
737
|
+
@connection.prepare nextkey, sql
|
738
|
+
rescue => e
|
739
|
+
raise translate_exception_class(e, sql, binds)
|
740
|
+
end
|
741
|
+
# Clear the queue
|
742
|
+
@connection.get_last_result
|
743
|
+
@statements[sql_key] = nextkey
|
744
|
+
end
|
745
|
+
@statements[sql_key]
|
1194
746
|
end
|
1195
|
-
@statements[sql_key]
|
1196
747
|
end
|
1197
748
|
|
1198
|
-
# The internal PostgreSQL identifier of the money data type.
|
1199
|
-
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
1200
|
-
# The internal PostgreSQL identifier of the BYTEA data type.
|
1201
|
-
BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
|
1202
|
-
|
1203
749
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
1204
750
|
# connected server's characteristics.
|
1205
751
|
def connect
|
1206
|
-
@connection =
|
1207
|
-
|
1208
|
-
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
1209
|
-
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
1210
|
-
# should know about this but can't detect it there, so deal with it here.
|
1211
|
-
PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
|
1212
|
-
|
752
|
+
@connection = PG.connect(@connection_parameters)
|
1213
753
|
configure_connection
|
754
|
+
add_pg_encoders
|
755
|
+
add_pg_decoders
|
1214
756
|
end
|
1215
757
|
|
1216
758
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -1219,45 +761,40 @@ module ActiveRecord
|
|
1219
761
|
if @config[:encoding]
|
1220
762
|
@connection.set_client_encoding(@config[:encoding])
|
1221
763
|
end
|
1222
|
-
self.client_min_messages = @config[:min_messages]
|
764
|
+
self.client_min_messages = @config[:min_messages] || "warning"
|
1223
765
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
1224
766
|
|
1225
|
-
# Use standard-conforming strings
|
767
|
+
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
1226
768
|
set_standard_conforming_strings
|
1227
769
|
|
770
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
771
|
+
|
1228
772
|
# If using Active Record's time zone support configure the connection to return
|
1229
773
|
# TIMESTAMP WITH ZONE types in UTC.
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
774
|
+
unless variables["timezone"]
|
775
|
+
if ActiveRecord::Base.default_timezone == :utc
|
776
|
+
variables["timezone"] = "UTC"
|
777
|
+
elsif @local_tz
|
778
|
+
variables["timezone"] = @local_tz
|
779
|
+
end
|
1234
780
|
end
|
1235
|
-
end
|
1236
|
-
|
1237
|
-
# Returns the current ID of a table's sequence.
|
1238
|
-
def last_insert_id(sequence_name) #:nodoc:
|
1239
|
-
r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
|
1240
|
-
Integer(r.rows.first.first)
|
1241
|
-
end
|
1242
781
|
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
res.clear
|
1254
|
-
return fields, results
|
782
|
+
# SET statements from :variables config hash
|
783
|
+
# https://www.postgresql.org/docs/current/static/sql-set.html
|
784
|
+
variables.map do |k, v|
|
785
|
+
if v == ":default" || v == :default
|
786
|
+
# Sets the value to the global or compile default
|
787
|
+
execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
|
788
|
+
elsif !v.nil?
|
789
|
+
execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
|
790
|
+
end
|
791
|
+
end
|
1255
792
|
end
|
1256
793
|
|
1257
794
|
# Returns the list of a table's column names, data types, and default values.
|
1258
795
|
#
|
1259
796
|
# The underlying query is roughly:
|
1260
|
-
# SELECT column.name, column.type, default.value
|
797
|
+
# SELECT column.name, column.type, default.value, column.comment
|
1261
798
|
# FROM column LEFT JOIN default
|
1262
799
|
# ON column.table_id = default.table_id
|
1263
800
|
# AND column.num = default.column_num
|
@@ -1272,35 +809,141 @@ module ActiveRecord
|
|
1272
809
|
# Query implementation notes:
|
1273
810
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1274
811
|
# - ::regclass is a function that gives the id for a table name
|
1275
|
-
def column_definitions(table_name)
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
812
|
+
def column_definitions(table_name)
|
813
|
+
query(<<~SQL, "SCHEMA")
|
814
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
815
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
816
|
+
c.collname, col_description(a.attrelid, a.attnum) AS comment
|
817
|
+
FROM pg_attribute a
|
818
|
+
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
819
|
+
LEFT JOIN pg_type t ON a.atttypid = t.oid
|
820
|
+
LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
|
821
|
+
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
|
822
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
823
|
+
ORDER BY a.attnum
|
824
|
+
SQL
|
1284
825
|
end
|
1285
826
|
|
1286
|
-
def
|
1287
|
-
|
827
|
+
def extract_table_ref_from_insert_sql(sql)
|
828
|
+
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
|
829
|
+
$1.strip if $1
|
830
|
+
end
|
1288
831
|
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
832
|
+
def arel_visitor
|
833
|
+
Arel::Visitors::PostgreSQL.new(self)
|
834
|
+
end
|
835
|
+
|
836
|
+
def build_statement_pool
|
837
|
+
StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
838
|
+
end
|
839
|
+
|
840
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
841
|
+
@case_insensitive_cache ||= {}
|
842
|
+
@case_insensitive_cache[column.sql_type] ||= begin
|
843
|
+
sql = <<~SQL
|
844
|
+
SELECT exists(
|
845
|
+
SELECT * FROM pg_proc
|
846
|
+
WHERE proname = 'lower'
|
847
|
+
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
|
848
|
+
) OR exists(
|
849
|
+
SELECT * FROM pg_proc
|
850
|
+
INNER JOIN pg_cast
|
851
|
+
ON ARRAY[casttarget]::oidvector = proargtypes
|
852
|
+
WHERE proname = 'lower'
|
853
|
+
AND castsource = #{quote column.sql_type}::regtype
|
854
|
+
)
|
855
|
+
SQL
|
856
|
+
execute_and_clear(sql, "SCHEMA", []) do |result|
|
857
|
+
result.getvalue(0, 0)
|
858
|
+
end
|
1293
859
|
end
|
1294
860
|
end
|
1295
861
|
|
1296
|
-
def
|
1297
|
-
|
1298
|
-
|
862
|
+
def add_pg_encoders
|
863
|
+
map = PG::TypeMapByClass.new
|
864
|
+
map[Integer] = PG::TextEncoder::Integer.new
|
865
|
+
map[TrueClass] = PG::TextEncoder::Boolean.new
|
866
|
+
map[FalseClass] = PG::TextEncoder::Boolean.new
|
867
|
+
@connection.type_map_for_queries = map
|
1299
868
|
end
|
1300
869
|
|
1301
|
-
def
|
1302
|
-
|
870
|
+
def update_typemap_for_default_timezone
|
871
|
+
if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
|
872
|
+
decoder_class = ActiveRecord::Base.default_timezone == :utc ?
|
873
|
+
PG::TextDecoder::TimestampUtc :
|
874
|
+
PG::TextDecoder::TimestampWithoutTimeZone
|
875
|
+
|
876
|
+
@timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
|
877
|
+
@connection.type_map_for_results.add_coder(@timestamp_decoder)
|
878
|
+
@default_timezone = ActiveRecord::Base.default_timezone
|
879
|
+
end
|
1303
880
|
end
|
881
|
+
|
882
|
+
def add_pg_decoders
|
883
|
+
@default_timezone = nil
|
884
|
+
@timestamp_decoder = nil
|
885
|
+
|
886
|
+
coders_by_name = {
|
887
|
+
"int2" => PG::TextDecoder::Integer,
|
888
|
+
"int4" => PG::TextDecoder::Integer,
|
889
|
+
"int8" => PG::TextDecoder::Integer,
|
890
|
+
"oid" => PG::TextDecoder::Integer,
|
891
|
+
"float4" => PG::TextDecoder::Float,
|
892
|
+
"float8" => PG::TextDecoder::Float,
|
893
|
+
"bool" => PG::TextDecoder::Boolean,
|
894
|
+
}
|
895
|
+
|
896
|
+
if defined?(PG::TextDecoder::TimestampUtc)
|
897
|
+
# Use native PG encoders available since pg-1.1
|
898
|
+
coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
|
899
|
+
coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
|
900
|
+
end
|
901
|
+
|
902
|
+
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
903
|
+
query = <<~SQL % known_coder_types.join(", ")
|
904
|
+
SELECT t.oid, t.typname
|
905
|
+
FROM pg_type as t
|
906
|
+
WHERE t.typname IN (%s)
|
907
|
+
SQL
|
908
|
+
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
909
|
+
result
|
910
|
+
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
911
|
+
.compact
|
912
|
+
end
|
913
|
+
|
914
|
+
map = PG::TypeMapByOid.new
|
915
|
+
coders.each { |coder| map.add_coder(coder) }
|
916
|
+
@connection.type_map_for_results = map
|
917
|
+
|
918
|
+
# extract timestamp decoder for use in update_typemap_for_default_timezone
|
919
|
+
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
|
920
|
+
update_typemap_for_default_timezone
|
921
|
+
end
|
922
|
+
|
923
|
+
def construct_coder(row, coder_class)
|
924
|
+
return unless coder_class
|
925
|
+
coder_class.new(oid: row["oid"].to_i, name: row["typname"])
|
926
|
+
end
|
927
|
+
|
928
|
+
ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
|
929
|
+
ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
|
930
|
+
ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
|
931
|
+
ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
|
932
|
+
ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
|
933
|
+
ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
|
934
|
+
ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql)
|
935
|
+
ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
|
936
|
+
ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
|
937
|
+
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
|
938
|
+
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
|
939
|
+
ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
|
940
|
+
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
|
941
|
+
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
|
942
|
+
ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
|
943
|
+
ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
|
944
|
+
ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
|
945
|
+
ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
|
946
|
+
ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
|
1304
947
|
end
|
1305
948
|
end
|
1306
949
|
end
|