activerecord 5.2.3
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 +937 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +217 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record.rb +188 -0
- data/lib/active_record/aggregations.rb +283 -0
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations.rb +1860 -0
- data/lib/active_record/associations/alias_tracker.rb +81 -0
- data/lib/active_record/associations/association.rb +299 -0
- data/lib/active_record/associations/association_scope.rb +168 -0
- data/lib/active_record/associations/belongs_to_association.rb +130 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +140 -0
- data/lib/active_record/associations/builder/belongs_to.rb +163 -0
- data/lib/active_record/associations/builder/collection_association.rb +82 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
- data/lib/active_record/associations/builder/has_many.rb +17 -0
- data/lib/active_record/associations/builder/has_one.rb +30 -0
- data/lib/active_record/associations/builder/singular_association.rb +42 -0
- data/lib/active_record/associations/collection_association.rb +513 -0
- data/lib/active_record/associations/collection_proxy.rb +1131 -0
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +144 -0
- data/lib/active_record/associations/has_many_through_association.rb +227 -0
- data/lib/active_record/associations/has_one_association.rb +120 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency.rb +262 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +193 -0
- data/lib/active_record/associations/preloader/association.rb +131 -0
- data/lib/active_record/associations/preloader/through_association.rb +107 -0
- data/lib/active_record/associations/singular_association.rb +73 -0
- data/lib/active_record/associations/through_association.rb +121 -0
- data/lib/active_record/attribute_assignment.rb +88 -0
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods.rb +492 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_record/attribute_methods/dirty.rb +150 -0
- data/lib/active_record/attribute_methods/primary_key.rb +143 -0
- data/lib/active_record/attribute_methods/query.rb +42 -0
- data/lib/active_record/attribute_methods/read.rb +85 -0
- data/lib/active_record/attribute_methods/serialization.rb +90 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_record/attribute_methods/write.rb +68 -0
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +498 -0
- data/lib/active_record/base.rb +329 -0
- data/lib/active_record/callbacks.rb +353 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +50 -0
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
- data/lib/active_record/connection_adapters/column.rb +91 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -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 +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -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/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -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 +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +218 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +122 -0
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +380 -0
- data/lib/active_record/explain.rb +50 -0
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +1065 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +283 -0
- data/lib/active_record/integration.rb +155 -0
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +198 -0
- data/lib/active_record/locking/pessimistic.rb +89 -0
- data/lib/active_record/log_subscriber.rb +137 -0
- data/lib/active_record/migration.rb +1378 -0
- data/lib/active_record/migration/command_recorder.rb +240 -0
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/model_schema.rb +521 -0
- data/lib/active_record/nested_attributes.rb +600 -0
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +763 -0
- data/lib/active_record/query_cache.rb +45 -0
- data/lib/active_record/querying.rb +70 -0
- data/lib/active_record/railtie.rb +226 -0
- data/lib/active_record/railties/console_sandbox.rb +7 -0
- data/lib/active_record/railties/controller_runtime.rb +56 -0
- data/lib/active_record/railties/databases.rake +377 -0
- data/lib/active_record/readonly_attributes.rb +24 -0
- data/lib/active_record/reflection.rb +1044 -0
- data/lib/active_record/relation.rb +629 -0
- data/lib/active_record/relation/batches.rb +287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/calculations.rb +417 -0
- data/lib/active_record/relation/delegation.rb +147 -0
- data/lib/active_record/relation/finder_methods.rb +565 -0
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder.rb +152 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1231 -0
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +77 -0
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/result.rb +149 -0
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +222 -0
- data/lib/active_record/schema.rb +70 -0
- data/lib/active_record/schema_dumper.rb +255 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +106 -0
- data/lib/active_record/scoping/default.rb +152 -0
- data/lib/active_record/scoping/named.rb +213 -0
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +211 -0
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +153 -0
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +502 -0
- data/lib/active_record/translation.rb +24 -0
- data/lib/active_record/type.rb +79 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -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_caster.rb +9 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/validations.rb +93 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +60 -0
- 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 +238 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -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.rb +35 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
- 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 +46 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- metadata +333 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
module ReferentialIntegrity # :nodoc:
|
7
|
+
def disable_referential_integrity # :nodoc:
|
8
|
+
original_exception = nil
|
9
|
+
|
10
|
+
begin
|
11
|
+
transaction(requires_new: true) do
|
12
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
13
|
+
end
|
14
|
+
rescue ActiveRecord::ActiveRecordError => e
|
15
|
+
original_exception = e
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
yield
|
20
|
+
rescue ActiveRecord::InvalidForeignKey => e
|
21
|
+
warn <<-WARNING
|
22
|
+
WARNING: Rails was not able to disable referential integrity.
|
23
|
+
|
24
|
+
This is most likely caused due to missing permissions.
|
25
|
+
Rails needs superuser privileges to disable referential integrity.
|
26
|
+
|
27
|
+
cause: #{original_exception.try(:message)}
|
28
|
+
|
29
|
+
WARNING
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
transaction(requires_new: true) do
|
35
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
36
|
+
end
|
37
|
+
rescue ActiveRecord::ActiveRecordError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
|
7
|
+
private
|
8
|
+
def visit_AlterTable(o)
|
9
|
+
super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit_AddForeignKey(o)
|
13
|
+
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_ValidateConstraint(name)
|
17
|
+
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_ChangeColumnDefinition(o)
|
21
|
+
column = o.column
|
22
|
+
column.sql_type = type_to_sql(column.type, column.options)
|
23
|
+
quoted_column_name = quote_column_name(o.name)
|
24
|
+
|
25
|
+
change_column_sql = "ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}".dup
|
26
|
+
|
27
|
+
options = column_options(column)
|
28
|
+
|
29
|
+
if options[:collation]
|
30
|
+
change_column_sql << " COLLATE \"#{options[:collation]}\""
|
31
|
+
end
|
32
|
+
|
33
|
+
if options[:using]
|
34
|
+
change_column_sql << " USING #{options[:using]}"
|
35
|
+
elsif options[:cast_as]
|
36
|
+
cast_as_type = type_to_sql(options[:cast_as], options)
|
37
|
+
change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
|
38
|
+
end
|
39
|
+
|
40
|
+
if options.key?(:default)
|
41
|
+
if options[:default].nil?
|
42
|
+
change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
|
43
|
+
else
|
44
|
+
quoted_default = quote_default_expression(options[:default], column)
|
45
|
+
change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if options.key?(:null)
|
50
|
+
change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
|
51
|
+
end
|
52
|
+
|
53
|
+
change_column_sql
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_column_options!(sql, options)
|
57
|
+
if options[:collation]
|
58
|
+
sql << " COLLATE \"#{options[:collation]}\""
|
59
|
+
end
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
module ColumnMethods
|
7
|
+
# Defines the primary key field.
|
8
|
+
# Use of the native PostgreSQL UUID type is supported, and can be used
|
9
|
+
# by defining your tables as such:
|
10
|
+
#
|
11
|
+
# create_table :stuffs, id: :uuid do |t|
|
12
|
+
# t.string :content
|
13
|
+
# t.timestamps
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# By default, this will use the +gen_random_uuid()+ function from the
|
17
|
+
# +pgcrypto+ extension. As that extension is only available in
|
18
|
+
# PostgreSQL 9.4+, for earlier versions an explicit default can be set
|
19
|
+
# to use +uuid_generate_v4()+ from the +uuid-ossp+ extension instead:
|
20
|
+
#
|
21
|
+
# create_table :stuffs, id: false do |t|
|
22
|
+
# t.primary_key :id, :uuid, default: "uuid_generate_v4()"
|
23
|
+
# t.uuid :foo_id
|
24
|
+
# t.timestamps
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# To enable the appropriate extension, which is a requirement, use
|
28
|
+
# the +enable_extension+ method in your migrations.
|
29
|
+
#
|
30
|
+
# To use a UUID primary key without any of the extensions, set the
|
31
|
+
# +:default+ option to +nil+:
|
32
|
+
#
|
33
|
+
# create_table :stuffs, id: false do |t|
|
34
|
+
# t.primary_key :id, :uuid, default: nil
|
35
|
+
# t.uuid :foo_id
|
36
|
+
# t.timestamps
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# You may also pass a custom stored procedure that returns a UUID or use a
|
40
|
+
# different UUID generation function from another library.
|
41
|
+
#
|
42
|
+
# Note that setting the UUID primary key default value to +nil+ will
|
43
|
+
# require you to assure that you always provide a UUID value before saving
|
44
|
+
# a record (as primary keys cannot be +nil+). This might be done via the
|
45
|
+
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
|
46
|
+
def primary_key(name, type = :primary_key, **options)
|
47
|
+
if type == :uuid
|
48
|
+
options[:default] = options.fetch(:default, "gen_random_uuid()")
|
49
|
+
end
|
50
|
+
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def bigserial(*args, **options)
|
55
|
+
args.each { |name| column(name, :bigserial, options) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def bit(*args, **options)
|
59
|
+
args.each { |name| column(name, :bit, options) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def bit_varying(*args, **options)
|
63
|
+
args.each { |name| column(name, :bit_varying, options) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def cidr(*args, **options)
|
67
|
+
args.each { |name| column(name, :cidr, options) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def citext(*args, **options)
|
71
|
+
args.each { |name| column(name, :citext, options) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def daterange(*args, **options)
|
75
|
+
args.each { |name| column(name, :daterange, options) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def hstore(*args, **options)
|
79
|
+
args.each { |name| column(name, :hstore, options) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def inet(*args, **options)
|
83
|
+
args.each { |name| column(name, :inet, options) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def interval(*args, **options)
|
87
|
+
args.each { |name| column(name, :interval, options) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def int4range(*args, **options)
|
91
|
+
args.each { |name| column(name, :int4range, options) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def int8range(*args, **options)
|
95
|
+
args.each { |name| column(name, :int8range, options) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def jsonb(*args, **options)
|
99
|
+
args.each { |name| column(name, :jsonb, options) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def ltree(*args, **options)
|
103
|
+
args.each { |name| column(name, :ltree, options) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def macaddr(*args, **options)
|
107
|
+
args.each { |name| column(name, :macaddr, options) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def money(*args, **options)
|
111
|
+
args.each { |name| column(name, :money, options) }
|
112
|
+
end
|
113
|
+
|
114
|
+
def numrange(*args, **options)
|
115
|
+
args.each { |name| column(name, :numrange, options) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def oid(*args, **options)
|
119
|
+
args.each { |name| column(name, :oid, options) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def point(*args, **options)
|
123
|
+
args.each { |name| column(name, :point, options) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def line(*args, **options)
|
127
|
+
args.each { |name| column(name, :line, options) }
|
128
|
+
end
|
129
|
+
|
130
|
+
def lseg(*args, **options)
|
131
|
+
args.each { |name| column(name, :lseg, options) }
|
132
|
+
end
|
133
|
+
|
134
|
+
def box(*args, **options)
|
135
|
+
args.each { |name| column(name, :box, options) }
|
136
|
+
end
|
137
|
+
|
138
|
+
def path(*args, **options)
|
139
|
+
args.each { |name| column(name, :path, options) }
|
140
|
+
end
|
141
|
+
|
142
|
+
def polygon(*args, **options)
|
143
|
+
args.each { |name| column(name, :polygon, options) }
|
144
|
+
end
|
145
|
+
|
146
|
+
def circle(*args, **options)
|
147
|
+
args.each { |name| column(name, :circle, options) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def serial(*args, **options)
|
151
|
+
args.each { |name| column(name, :serial, options) }
|
152
|
+
end
|
153
|
+
|
154
|
+
def tsrange(*args, **options)
|
155
|
+
args.each { |name| column(name, :tsrange, options) }
|
156
|
+
end
|
157
|
+
|
158
|
+
def tstzrange(*args, **options)
|
159
|
+
args.each { |name| column(name, :tstzrange, options) }
|
160
|
+
end
|
161
|
+
|
162
|
+
def tsvector(*args, **options)
|
163
|
+
args.each { |name| column(name, :tsvector, options) }
|
164
|
+
end
|
165
|
+
|
166
|
+
def uuid(*args, **options)
|
167
|
+
args.each { |name| column(name, :uuid, options) }
|
168
|
+
end
|
169
|
+
|
170
|
+
def xml(*args, **options)
|
171
|
+
args.each { |name| column(name, :xml, options) }
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
176
|
+
include ColumnMethods
|
177
|
+
|
178
|
+
private
|
179
|
+
def integer_like_primary_key_type(type, options)
|
180
|
+
if type == :bigint || options[:limit] == 8
|
181
|
+
:bigserial
|
182
|
+
else
|
183
|
+
:serial
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Table < ActiveRecord::ConnectionAdapters::Table
|
189
|
+
include ColumnMethods
|
190
|
+
end
|
191
|
+
|
192
|
+
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
193
|
+
attr_reader :constraint_validations
|
194
|
+
|
195
|
+
def initialize(td)
|
196
|
+
super
|
197
|
+
@constraint_validations = []
|
198
|
+
end
|
199
|
+
|
200
|
+
def validate_constraint(name)
|
201
|
+
@constraint_validations << name
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
7
|
+
private
|
8
|
+
|
9
|
+
def extensions(stream)
|
10
|
+
extensions = @connection.extensions
|
11
|
+
if extensions.any?
|
12
|
+
stream.puts " # These are extensions that must be enabled in order to support this database"
|
13
|
+
extensions.sort.each do |extension|
|
14
|
+
stream.puts " enable_extension #{extension.inspect}"
|
15
|
+
end
|
16
|
+
stream.puts
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def prepare_column_options(column)
|
21
|
+
spec = super
|
22
|
+
spec[:array] = "true" if column.array?
|
23
|
+
spec
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_primary_key?(column)
|
27
|
+
schema_type(column) == :bigserial
|
28
|
+
end
|
29
|
+
|
30
|
+
def explicit_primary_key_default?(column)
|
31
|
+
column.type == :uuid || (column.type == :integer && !column.serial?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def schema_type(column)
|
35
|
+
return super unless column.serial?
|
36
|
+
|
37
|
+
if column.bigint?
|
38
|
+
:bigserial
|
39
|
+
else
|
40
|
+
:serial
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def schema_expression(column)
|
45
|
+
super unless column.serial?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,774 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
module SchemaStatements
|
7
|
+
# Drops the database specified on the +name+ attribute
|
8
|
+
# and creates it again using the provided +options+.
|
9
|
+
def recreate_database(name, options = {}) #:nodoc:
|
10
|
+
drop_database(name)
|
11
|
+
create_database(name, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
|
15
|
+
# <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
|
16
|
+
# <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
|
17
|
+
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
|
18
|
+
#
|
19
|
+
# Example:
|
20
|
+
# create_database config[:database], config
|
21
|
+
# create_database 'foo_development', encoding: 'unicode'
|
22
|
+
def create_database(name, options = {})
|
23
|
+
options = { encoding: "utf8" }.merge!(options.symbolize_keys)
|
24
|
+
|
25
|
+
option_string = options.inject("") do |memo, (key, value)|
|
26
|
+
memo += case key
|
27
|
+
when :owner
|
28
|
+
" OWNER = \"#{value}\""
|
29
|
+
when :template
|
30
|
+
" TEMPLATE = \"#{value}\""
|
31
|
+
when :encoding
|
32
|
+
" ENCODING = '#{value}'"
|
33
|
+
when :collation
|
34
|
+
" LC_COLLATE = '#{value}'"
|
35
|
+
when :ctype
|
36
|
+
" LC_CTYPE = '#{value}'"
|
37
|
+
when :tablespace
|
38
|
+
" TABLESPACE = \"#{value}\""
|
39
|
+
when :connection_limit
|
40
|
+
" CONNECTION LIMIT = #{value}"
|
41
|
+
else
|
42
|
+
""
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Drops a PostgreSQL database.
|
50
|
+
#
|
51
|
+
# Example:
|
52
|
+
# drop_database 'matt_development'
|
53
|
+
def drop_database(name) #:nodoc:
|
54
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def drop_table(table_name, options = {}) # :nodoc:
|
58
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns true if schema exists.
|
62
|
+
def schema_exists?(name)
|
63
|
+
query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
|
64
|
+
end
|
65
|
+
|
66
|
+
# Verifies existence of an index with a given name.
|
67
|
+
def index_name_exists?(table_name, index_name)
|
68
|
+
table = quoted_scope(table_name)
|
69
|
+
index = quoted_scope(index_name)
|
70
|
+
|
71
|
+
query_value(<<-SQL, "SCHEMA").to_i > 0
|
72
|
+
SELECT COUNT(*)
|
73
|
+
FROM pg_class t
|
74
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
75
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
76
|
+
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
77
|
+
WHERE i.relkind = 'i'
|
78
|
+
AND i.relname = #{index[:name]}
|
79
|
+
AND t.relname = #{table[:name]}
|
80
|
+
AND n.nspname = #{index[:schema]}
|
81
|
+
SQL
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns an array of indexes for the given table.
|
85
|
+
def indexes(table_name) # :nodoc:
|
86
|
+
scope = quoted_scope(table_name)
|
87
|
+
|
88
|
+
result = query(<<-SQL, "SCHEMA")
|
89
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
90
|
+
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
91
|
+
FROM pg_class t
|
92
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
93
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
94
|
+
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
95
|
+
WHERE i.relkind = 'i'
|
96
|
+
AND d.indisprimary = 'f'
|
97
|
+
AND t.relname = #{scope[:name]}
|
98
|
+
AND n.nspname = #{scope[:schema]}
|
99
|
+
ORDER BY i.relname
|
100
|
+
SQL
|
101
|
+
|
102
|
+
result.map do |row|
|
103
|
+
index_name = row[0]
|
104
|
+
unique = row[1]
|
105
|
+
indkey = row[2].split(" ").map(&:to_i)
|
106
|
+
inddef = row[3]
|
107
|
+
oid = row[4]
|
108
|
+
comment = row[5]
|
109
|
+
|
110
|
+
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
|
111
|
+
|
112
|
+
orders = {}
|
113
|
+
opclasses = {}
|
114
|
+
|
115
|
+
if indkey.include?(0)
|
116
|
+
columns = expressions
|
117
|
+
else
|
118
|
+
columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
|
119
|
+
SELECT a.attnum, a.attname
|
120
|
+
FROM pg_attribute a
|
121
|
+
WHERE a.attrelid = #{oid}
|
122
|
+
AND a.attnum IN (#{indkey.join(",")})
|
123
|
+
SQL
|
124
|
+
|
125
|
+
# add info on sort order (only desc order is explicitly specified, asc is the default)
|
126
|
+
# and non-default opclasses
|
127
|
+
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
|
128
|
+
opclasses[column] = opclass.to_sym if opclass
|
129
|
+
if nulls
|
130
|
+
orders[column] = [desc, nulls].compact.join(" ")
|
131
|
+
else
|
132
|
+
orders[column] = :desc if desc
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
IndexDefinition.new(
|
138
|
+
table_name,
|
139
|
+
index_name,
|
140
|
+
unique,
|
141
|
+
columns,
|
142
|
+
orders: orders,
|
143
|
+
opclasses: opclasses,
|
144
|
+
where: where,
|
145
|
+
using: using.to_sym,
|
146
|
+
comment: comment.presence
|
147
|
+
)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def table_options(table_name) # :nodoc:
|
152
|
+
if comment = table_comment(table_name)
|
153
|
+
{ comment: comment }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns a comment stored in database for given table
|
158
|
+
def table_comment(table_name) # :nodoc:
|
159
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
160
|
+
if scope[:name]
|
161
|
+
query_value(<<-SQL.strip_heredoc, "SCHEMA")
|
162
|
+
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
|
163
|
+
FROM pg_catalog.pg_class c
|
164
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
165
|
+
WHERE c.relname = #{scope[:name]}
|
166
|
+
AND c.relkind IN (#{scope[:type]})
|
167
|
+
AND n.nspname = #{scope[:schema]}
|
168
|
+
SQL
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns the current database name.
|
173
|
+
def current_database
|
174
|
+
query_value("SELECT current_database()", "SCHEMA")
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the current schema name.
|
178
|
+
def current_schema
|
179
|
+
query_value("SELECT current_schema", "SCHEMA")
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns the current database encoding format.
|
183
|
+
def encoding
|
184
|
+
query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns the current database collation.
|
188
|
+
def collation
|
189
|
+
query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns the current database ctype.
|
193
|
+
def ctype
|
194
|
+
query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns an array of schema names.
|
198
|
+
def schema_names
|
199
|
+
query_values(<<-SQL, "SCHEMA")
|
200
|
+
SELECT nspname
|
201
|
+
FROM pg_namespace
|
202
|
+
WHERE nspname !~ '^pg_.*'
|
203
|
+
AND nspname NOT IN ('information_schema')
|
204
|
+
ORDER by nspname;
|
205
|
+
SQL
|
206
|
+
end
|
207
|
+
|
208
|
+
# Creates a schema for the given schema name.
|
209
|
+
def create_schema(schema_name)
|
210
|
+
execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
|
211
|
+
end
|
212
|
+
|
213
|
+
# Drops the schema for the given schema name.
|
214
|
+
def drop_schema(schema_name, options = {})
|
215
|
+
execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
|
216
|
+
end
|
217
|
+
|
218
|
+
# Sets the schema search path to a string of comma-separated schema names.
|
219
|
+
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
220
|
+
# See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
|
221
|
+
#
|
222
|
+
# This should be not be called manually but set in database.yml.
|
223
|
+
def schema_search_path=(schema_csv)
|
224
|
+
if schema_csv
|
225
|
+
execute("SET search_path TO #{schema_csv}", "SCHEMA")
|
226
|
+
@schema_search_path = schema_csv
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns the active schema search path.
|
231
|
+
def schema_search_path
|
232
|
+
@schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns the current client message level.
|
236
|
+
def client_min_messages
|
237
|
+
query_value("SHOW client_min_messages", "SCHEMA")
|
238
|
+
end
|
239
|
+
|
240
|
+
# Set the client message level.
|
241
|
+
def client_min_messages=(level)
|
242
|
+
execute("SET client_min_messages TO '#{level}'", "SCHEMA")
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns the sequence name for a table's primary key or some other specified key.
|
246
|
+
def default_sequence_name(table_name, pk = "id") #:nodoc:
|
247
|
+
result = serial_sequence(table_name, pk)
|
248
|
+
return nil unless result
|
249
|
+
Utils.extract_schema_qualified_name(result).to_s
|
250
|
+
rescue ActiveRecord::StatementInvalid
|
251
|
+
PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
|
252
|
+
end
|
253
|
+
|
254
|
+
def serial_sequence(table, column)
|
255
|
+
query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
|
256
|
+
end
|
257
|
+
|
258
|
+
# Sets the sequence of a table's primary key to the specified value.
|
259
|
+
def set_pk_sequence!(table, value) #:nodoc:
|
260
|
+
pk, sequence = pk_and_sequence_for(table)
|
261
|
+
|
262
|
+
if pk
|
263
|
+
if sequence
|
264
|
+
quoted_sequence = quote_table_name(sequence)
|
265
|
+
|
266
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
|
267
|
+
else
|
268
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Resets the sequence of a table's primary key to the maximum value.
|
274
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
275
|
+
unless pk && sequence
|
276
|
+
default_pk, default_sequence = pk_and_sequence_for(table)
|
277
|
+
|
278
|
+
pk ||= default_pk
|
279
|
+
sequence ||= default_sequence
|
280
|
+
end
|
281
|
+
|
282
|
+
if @logger && pk && !sequence
|
283
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence."
|
284
|
+
end
|
285
|
+
|
286
|
+
if pk && sequence
|
287
|
+
quoted_sequence = quote_table_name(sequence)
|
288
|
+
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
289
|
+
if max_pk.nil?
|
290
|
+
if postgresql_version >= 100000
|
291
|
+
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
292
|
+
else
|
293
|
+
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Returns a table's primary key and belonging sequence.
|
302
|
+
def pk_and_sequence_for(table) #:nodoc:
|
303
|
+
# First try looking for a sequence with a dependency on the
|
304
|
+
# given table's primary key.
|
305
|
+
result = query(<<-end_sql, "SCHEMA")[0]
|
306
|
+
SELECT attr.attname, nsp.nspname, seq.relname
|
307
|
+
FROM pg_class seq,
|
308
|
+
pg_attribute attr,
|
309
|
+
pg_depend dep,
|
310
|
+
pg_constraint cons,
|
311
|
+
pg_namespace nsp
|
312
|
+
WHERE seq.oid = dep.objid
|
313
|
+
AND seq.relkind = 'S'
|
314
|
+
AND attr.attrelid = dep.refobjid
|
315
|
+
AND attr.attnum = dep.refobjsubid
|
316
|
+
AND attr.attrelid = cons.conrelid
|
317
|
+
AND attr.attnum = cons.conkey[1]
|
318
|
+
AND seq.relnamespace = nsp.oid
|
319
|
+
AND cons.contype = 'p'
|
320
|
+
AND dep.classid = 'pg_class'::regclass
|
321
|
+
AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
|
322
|
+
end_sql
|
323
|
+
|
324
|
+
if result.nil? || result.empty?
|
325
|
+
result = query(<<-end_sql, "SCHEMA")[0]
|
326
|
+
SELECT attr.attname, nsp.nspname,
|
327
|
+
CASE
|
328
|
+
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
|
329
|
+
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
330
|
+
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
331
|
+
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
332
|
+
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
333
|
+
END
|
334
|
+
FROM pg_class t
|
335
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
336
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
337
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
338
|
+
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
|
339
|
+
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
|
340
|
+
AND cons.contype = 'p'
|
341
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
342
|
+
end_sql
|
343
|
+
end
|
344
|
+
|
345
|
+
pk = result.shift
|
346
|
+
if result.last
|
347
|
+
[pk, PostgreSQL::Name.new(*result)]
|
348
|
+
else
|
349
|
+
[pk, nil]
|
350
|
+
end
|
351
|
+
rescue
|
352
|
+
nil
|
353
|
+
end
|
354
|
+
|
355
|
+
def primary_keys(table_name) # :nodoc:
|
356
|
+
query_values(<<-SQL.strip_heredoc, "SCHEMA")
|
357
|
+
SELECT a.attname
|
358
|
+
FROM (
|
359
|
+
SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
|
360
|
+
FROM pg_index
|
361
|
+
WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
|
362
|
+
AND indisprimary
|
363
|
+
) i
|
364
|
+
JOIN pg_attribute a
|
365
|
+
ON a.attrelid = i.indrelid
|
366
|
+
AND a.attnum = i.indkey[i.idx]
|
367
|
+
ORDER BY i.idx
|
368
|
+
SQL
|
369
|
+
end
|
370
|
+
|
371
|
+
def bulk_change_table(table_name, operations)
|
372
|
+
sql_fragments = []
|
373
|
+
non_combinable_operations = []
|
374
|
+
|
375
|
+
operations.each do |command, args|
|
376
|
+
table, arguments = args.shift, args
|
377
|
+
method = :"#{command}_for_alter"
|
378
|
+
|
379
|
+
if respond_to?(method, true)
|
380
|
+
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
381
|
+
sql_fragments << sqls
|
382
|
+
non_combinable_operations.concat(procs)
|
383
|
+
else
|
384
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
385
|
+
non_combinable_operations.each(&:call)
|
386
|
+
sql_fragments = []
|
387
|
+
non_combinable_operations = []
|
388
|
+
send(command, table, *arguments)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
393
|
+
non_combinable_operations.each(&:call)
|
394
|
+
end
|
395
|
+
|
396
|
+
# Renames a table.
|
397
|
+
# Also renames a table's primary key sequence if the sequence name exists and
|
398
|
+
# matches the Active Record default.
|
399
|
+
#
|
400
|
+
# Example:
|
401
|
+
# rename_table('octopuses', 'octopi')
|
402
|
+
def rename_table(table_name, new_name)
|
403
|
+
clear_cache!
|
404
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
405
|
+
pk, seq = pk_and_sequence_for(new_name)
|
406
|
+
if pk
|
407
|
+
idx = "#{table_name}_pkey"
|
408
|
+
new_idx = "#{new_name}_pkey"
|
409
|
+
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
410
|
+
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
|
411
|
+
new_seq = "#{new_name}_#{pk}_seq"
|
412
|
+
execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
|
413
|
+
end
|
414
|
+
end
|
415
|
+
rename_table_indexes(table_name, new_name)
|
416
|
+
end
|
417
|
+
|
418
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
419
|
+
clear_cache!
|
420
|
+
super
|
421
|
+
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
422
|
+
end
|
423
|
+
|
424
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
425
|
+
clear_cache!
|
426
|
+
sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
|
427
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
|
428
|
+
procs.each(&:call)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Changes the default value of a table column.
|
432
|
+
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
433
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
434
|
+
end
|
435
|
+
|
436
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
437
|
+
clear_cache!
|
438
|
+
unless null || default.nil?
|
439
|
+
column = column_for(table_name, column_name)
|
440
|
+
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
|
441
|
+
end
|
442
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
|
443
|
+
end
|
444
|
+
|
445
|
+
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
446
|
+
def change_column_comment(table_name, column_name, comment) # :nodoc:
|
447
|
+
clear_cache!
|
448
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
|
449
|
+
end
|
450
|
+
|
451
|
+
# Adds comment for given table or drops it if +comment+ is a +nil+
|
452
|
+
def change_table_comment(table_name, comment) # :nodoc:
|
453
|
+
clear_cache!
|
454
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
|
455
|
+
end
|
456
|
+
|
457
|
+
# Renames a column in a table.
|
458
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
459
|
+
clear_cache!
|
460
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
461
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
462
|
+
end
|
463
|
+
|
464
|
+
def add_index(table_name, column_name, options = {}) #:nodoc:
|
465
|
+
index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
466
|
+
execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
|
467
|
+
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
472
|
+
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
473
|
+
|
474
|
+
if options.is_a?(Hash) && options.key?(:name)
|
475
|
+
provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
|
476
|
+
|
477
|
+
options[:name] = provided_index.identifier
|
478
|
+
table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
|
479
|
+
|
480
|
+
if provided_index.schema.present? && table.schema != provided_index.schema
|
481
|
+
raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
|
486
|
+
algorithm =
|
487
|
+
if options.is_a?(Hash) && options.key?(:algorithm)
|
488
|
+
index_algorithms.fetch(options[:algorithm]) do
|
489
|
+
raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
|
490
|
+
end
|
491
|
+
end
|
492
|
+
execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
|
493
|
+
end
|
494
|
+
|
495
|
+
# Renames an index of a table. Raises error if length of new
|
496
|
+
# index name is greater than allowed limit.
|
497
|
+
def rename_index(table_name, old_name, new_name)
|
498
|
+
validate_index_length!(table_name, new_name)
|
499
|
+
|
500
|
+
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
501
|
+
end
|
502
|
+
|
503
|
+
def foreign_keys(table_name)
|
504
|
+
scope = quoted_scope(table_name)
|
505
|
+
fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
|
506
|
+
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
|
507
|
+
FROM pg_constraint c
|
508
|
+
JOIN pg_class t1 ON c.conrelid = t1.oid
|
509
|
+
JOIN pg_class t2 ON c.confrelid = t2.oid
|
510
|
+
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
511
|
+
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
512
|
+
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
513
|
+
WHERE c.contype = 'f'
|
514
|
+
AND t1.relname = #{scope[:name]}
|
515
|
+
AND t3.nspname = #{scope[:schema]}
|
516
|
+
ORDER BY c.conname
|
517
|
+
SQL
|
518
|
+
|
519
|
+
fk_info.map do |row|
|
520
|
+
options = {
|
521
|
+
column: row["column"],
|
522
|
+
name: row["name"],
|
523
|
+
primary_key: row["primary_key"]
|
524
|
+
}
|
525
|
+
|
526
|
+
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
527
|
+
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
528
|
+
options[:validate] = row["valid"]
|
529
|
+
|
530
|
+
ForeignKeyDefinition.new(table_name, row["to_table"], options)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def foreign_tables
|
535
|
+
query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
|
536
|
+
end
|
537
|
+
|
538
|
+
def foreign_table_exists?(table_name)
|
539
|
+
query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
|
540
|
+
end
|
541
|
+
|
542
|
+
# Maps logical Rails types to PostgreSQL-specific data types.
|
543
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
544
|
+
sql = \
|
545
|
+
case type.to_s
|
546
|
+
when "binary"
|
547
|
+
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
548
|
+
# The hard limit is 1GB, because of a 32-bit size field, and TOAST.
|
549
|
+
case limit
|
550
|
+
when nil, 0..0x3fffffff; super(type)
|
551
|
+
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
552
|
+
end
|
553
|
+
when "text"
|
554
|
+
# PostgreSQL doesn't support limits on text columns.
|
555
|
+
# The hard limit is 1GB, according to section 8.3 in the manual.
|
556
|
+
case limit
|
557
|
+
when nil, 0..0x3fffffff; super(type)
|
558
|
+
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
559
|
+
end
|
560
|
+
when "integer"
|
561
|
+
case limit
|
562
|
+
when 1, 2; "smallint"
|
563
|
+
when nil, 3, 4; "integer"
|
564
|
+
when 5..8; "bigint"
|
565
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
|
566
|
+
end
|
567
|
+
else
|
568
|
+
super
|
569
|
+
end
|
570
|
+
|
571
|
+
sql = "#{sql}[]" if array && type != :primary_key
|
572
|
+
sql
|
573
|
+
end
|
574
|
+
|
575
|
+
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
576
|
+
# requires that the ORDER BY include the distinct column.
|
577
|
+
def columns_for_distinct(columns, orders) #:nodoc:
|
578
|
+
order_columns = orders.reject(&:blank?).map { |s|
|
579
|
+
# Convert Arel node to string
|
580
|
+
s = s.to_sql unless s.is_a?(String)
|
581
|
+
# Remove any ASC/DESC modifiers
|
582
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
583
|
+
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
|
584
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
585
|
+
|
586
|
+
(order_columns << super).join(", ")
|
587
|
+
end
|
588
|
+
|
589
|
+
def update_table_definition(table_name, base) # :nodoc:
|
590
|
+
PostgreSQL::Table.new(table_name, base)
|
591
|
+
end
|
592
|
+
|
593
|
+
def create_schema_dumper(options) # :nodoc:
|
594
|
+
PostgreSQL::SchemaDumper.create(self, options)
|
595
|
+
end
|
596
|
+
|
597
|
+
# Validates the given constraint.
|
598
|
+
#
|
599
|
+
# Validates the constraint named +constraint_name+ on +accounts+.
|
600
|
+
#
|
601
|
+
# validate_constraint :accounts, :constraint_name
|
602
|
+
def validate_constraint(table_name, constraint_name)
|
603
|
+
return unless supports_validate_constraints?
|
604
|
+
|
605
|
+
at = create_alter_table table_name
|
606
|
+
at.validate_constraint constraint_name
|
607
|
+
|
608
|
+
execute schema_creation.accept(at)
|
609
|
+
end
|
610
|
+
|
611
|
+
# Validates the given foreign key.
|
612
|
+
#
|
613
|
+
# Validates the foreign key on +accounts.branch_id+.
|
614
|
+
#
|
615
|
+
# validate_foreign_key :accounts, :branches
|
616
|
+
#
|
617
|
+
# Validates the foreign key on +accounts.owner_id+.
|
618
|
+
#
|
619
|
+
# validate_foreign_key :accounts, column: :owner_id
|
620
|
+
#
|
621
|
+
# Validates the foreign key named +special_fk_name+ on the +accounts+ table.
|
622
|
+
#
|
623
|
+
# validate_foreign_key :accounts, name: :special_fk_name
|
624
|
+
#
|
625
|
+
# The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
|
626
|
+
def validate_foreign_key(from_table, options_or_to_table = {})
|
627
|
+
return unless supports_validate_constraints?
|
628
|
+
|
629
|
+
fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name
|
630
|
+
|
631
|
+
validate_constraint from_table, fk_name_to_validate
|
632
|
+
end
|
633
|
+
|
634
|
+
private
|
635
|
+
def schema_creation
|
636
|
+
PostgreSQL::SchemaCreation.new(self)
|
637
|
+
end
|
638
|
+
|
639
|
+
def create_table_definition(*args)
|
640
|
+
PostgreSQL::TableDefinition.new(*args)
|
641
|
+
end
|
642
|
+
|
643
|
+
def create_alter_table(name)
|
644
|
+
PostgreSQL::AlterTable.new create_table_definition(name)
|
645
|
+
end
|
646
|
+
|
647
|
+
def new_column_from_field(table_name, field)
|
648
|
+
column_name, type, default, notnull, oid, fmod, collation, comment = field
|
649
|
+
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
650
|
+
default_value = extract_value_from_default(default)
|
651
|
+
default_function = extract_default_function(default_value, default)
|
652
|
+
|
653
|
+
PostgreSQLColumn.new(
|
654
|
+
column_name,
|
655
|
+
default_value,
|
656
|
+
type_metadata,
|
657
|
+
!notnull,
|
658
|
+
table_name,
|
659
|
+
default_function,
|
660
|
+
collation,
|
661
|
+
comment: comment.presence,
|
662
|
+
max_identifier_length: max_identifier_length
|
663
|
+
)
|
664
|
+
end
|
665
|
+
|
666
|
+
def fetch_type_metadata(column_name, sql_type, oid, fmod)
|
667
|
+
cast_type = get_oid_type(oid, fmod, column_name, sql_type)
|
668
|
+
simple_type = SqlTypeMetadata.new(
|
669
|
+
sql_type: sql_type,
|
670
|
+
type: cast_type.type,
|
671
|
+
limit: cast_type.limit,
|
672
|
+
precision: cast_type.precision,
|
673
|
+
scale: cast_type.scale,
|
674
|
+
)
|
675
|
+
PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
|
676
|
+
end
|
677
|
+
|
678
|
+
def extract_foreign_key_action(specifier)
|
679
|
+
case specifier
|
680
|
+
when "c"; :cascade
|
681
|
+
when "n"; :nullify
|
682
|
+
when "r"; :restrict
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
def add_column_for_alter(table_name, column_name, type, options = {})
|
687
|
+
return super unless options.key?(:comment)
|
688
|
+
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
|
689
|
+
end
|
690
|
+
|
691
|
+
def change_column_for_alter(table_name, column_name, type, options = {})
|
692
|
+
td = create_table_definition(table_name)
|
693
|
+
cd = td.new_column_definition(column_name, type, options)
|
694
|
+
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
695
|
+
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
696
|
+
sqls
|
697
|
+
end
|
698
|
+
|
699
|
+
def change_column_default_for_alter(table_name, column_name, default_or_changes)
|
700
|
+
column = column_for(table_name, column_name)
|
701
|
+
return unless column
|
702
|
+
|
703
|
+
default = extract_new_default_value(default_or_changes)
|
704
|
+
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
|
705
|
+
if default.nil?
|
706
|
+
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
|
707
|
+
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
708
|
+
alter_column_query % "DROP DEFAULT"
|
709
|
+
else
|
710
|
+
alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
715
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
716
|
+
end
|
717
|
+
|
718
|
+
def add_timestamps_for_alter(table_name, options = {})
|
719
|
+
[add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
|
720
|
+
end
|
721
|
+
|
722
|
+
def remove_timestamps_for_alter(table_name, options = {})
|
723
|
+
[remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
|
724
|
+
end
|
725
|
+
|
726
|
+
def add_index_opclass(quoted_columns, **options)
|
727
|
+
opclasses = options_for_index_columns(options[:opclass])
|
728
|
+
quoted_columns.each do |name, column|
|
729
|
+
column << " #{opclasses[name]}" if opclasses[name].present?
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
def add_options_for_index_columns(quoted_columns, **options)
|
734
|
+
quoted_columns = add_index_opclass(quoted_columns, options)
|
735
|
+
super
|
736
|
+
end
|
737
|
+
|
738
|
+
def data_source_sql(name = nil, type: nil)
|
739
|
+
scope = quoted_scope(name, type: type)
|
740
|
+
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
741
|
+
|
742
|
+
sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
|
743
|
+
sql << " WHERE n.nspname = #{scope[:schema]}"
|
744
|
+
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
|
745
|
+
sql << " AND c.relkind IN (#{scope[:type]})"
|
746
|
+
sql
|
747
|
+
end
|
748
|
+
|
749
|
+
def quoted_scope(name = nil, type: nil)
|
750
|
+
schema, name = extract_schema_qualified_name(name)
|
751
|
+
type = \
|
752
|
+
case type
|
753
|
+
when "BASE TABLE"
|
754
|
+
"'r','p'"
|
755
|
+
when "VIEW"
|
756
|
+
"'v','m'"
|
757
|
+
when "FOREIGN TABLE"
|
758
|
+
"'f'"
|
759
|
+
end
|
760
|
+
scope = {}
|
761
|
+
scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
|
762
|
+
scope[:name] = quote(name) if name
|
763
|
+
scope[:type] = type if type
|
764
|
+
scope
|
765
|
+
end
|
766
|
+
|
767
|
+
def extract_schema_qualified_name(string)
|
768
|
+
name = Utils.extract_schema_qualified_name(string.to_s)
|
769
|
+
[name.schema, name.identifier]
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|
773
|
+
end
|
774
|
+
end
|