未验证 提交 ee651813 编写于 作者: R Ryuta Kamizono 提交者: GitHub

Merge pull request #37585 from sebastian-palma/upsert-all-partitioned-indexes

Add support for partitioned indexes in PostgreSQL 11+
* Add support for PostgreSQL 11+ partitioned indexes when using `upsert_all`.
*Sebastián Palma*
* Adds support for `if_not_exists` to `add_column` and `if_exists` to `remove_column`.
Applications can set their migrations to ignore exceptions raised when adding a column that already exists or when removing a column that does not exist.
......
......@@ -283,6 +283,10 @@ def prefetch_primary_key?(table_name = nil)
false
end
def supports_partitioned_indexes?
false
end
# Does this adapter support index sort order?
def supports_index_sort_order?
false
......
......@@ -75,7 +75,7 @@ def index_name_exists?(table_name, index_name)
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
WHERE i.relkind = 'i'
WHERE i.relkind IN ('i', 'I')
AND i.relname = #{index[:name]}
AND t.relname = #{table[:name]}
AND n.nspname = #{index[:schema]}
......@@ -93,7 +93,7 @@ def indexes(table_name) # :nodoc:
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
WHERE i.relkind = 'i'
WHERE i.relkind IN ('i', 'I')
AND d.indisprimary = 'f'
AND t.relname = #{scope[:name]}
AND n.nspname = #{scope[:schema]}
......
......@@ -157,6 +157,10 @@ def supports_index_sort_order?
true
end
def supports_partitioned_indexes?
database_version >= 110_000
end
def supports_partial_index?
true
end
......
......@@ -45,6 +45,8 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
PK_TABLE_NAME = "table_with_pk"
UNMATCHED_SEQUENCE_NAME = "unmatched_primary_key_default_value_seq"
UNMATCHED_PK_TABLE_NAME = "table_with_unmatched_sequence_for_pk"
PARTITIONED_TABLE = "measurements"
PARTITIONED_TABLE_INDEX = "index_measurements_on_logdate_and_city_id"
class Thing1 < ActiveRecord::Base
self.table_name = "test_schema.things"
......@@ -311,6 +313,12 @@ def test_index_name_exists
assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME)
assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME)
assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index")
if supports_partitioned_indexes?
create_partitioned_table
create_partitioned_table_index
assert @connection.index_name_exists?(PARTITIONED_TABLE, PARTITIONED_TABLE_INDEX)
end
end
end
......@@ -329,6 +337,13 @@ def test_dump_indexes_for_schema_multiple_schemas_in_search_path
def test_dump_indexes_for_table_with_scheme_specified_in_name
indexes = @connection.indexes("#{SCHEMA_NAME}.#{TABLE_NAME}")
assert_equal 5, indexes.size
if supports_partitioned_indexes?
create_partitioned_table
create_partitioned_table_index
indexes = @connection.indexes("#{SCHEMA_NAME}.#{PARTITIONED_TABLE}")
assert_equal 1, indexes.size
end
end
def test_with_uppercase_index_name
......@@ -337,6 +352,15 @@ def test_with_uppercase_index_name
with_schema_search_path SCHEMA_NAME do
assert_nothing_raised { @connection.remove_index "things", name: "things_Index" }
end
if supports_partitioned_indexes?
create_partitioned_table
@connection.execute "CREATE INDEX \"#{PARTITIONED_TABLE}_Index\" ON #{SCHEMA_NAME}.#{PARTITIONED_TABLE} (logdate, city_id)"
with_schema_search_path SCHEMA_NAME do
assert_nothing_raised { @connection.remove_index PARTITIONED_TABLE, name: "#{PARTITIONED_TABLE}_Index" }
end
end
end
def test_remove_index_when_schema_specified
......@@ -351,6 +375,22 @@ def test_remove_index_when_schema_specified
@connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
assert_raises(ArgumentError) { @connection.remove_index "#{SCHEMA2_NAME}.things", name: "#{SCHEMA_NAME}.things_Index" }
if supports_partitioned_indexes?
create_partitioned_table
@connection.execute "CREATE INDEX \"#{PARTITIONED_TABLE}_Index\" ON #{SCHEMA_NAME}.#{PARTITIONED_TABLE} (logdate, city_id)"
assert_nothing_raised { @connection.remove_index PARTITIONED_TABLE, name: "#{SCHEMA_NAME}.#{PARTITIONED_TABLE}_Index" }
@connection.execute "CREATE INDEX \"#{PARTITIONED_TABLE}_Index\" ON #{SCHEMA_NAME}.#{PARTITIONED_TABLE} (logdate, city_id)"
assert_nothing_raised { @connection.remove_index "#{SCHEMA_NAME}.#{PARTITIONED_TABLE}", name: "#{PARTITIONED_TABLE}_Index" }
@connection.execute "CREATE INDEX \"#{PARTITIONED_TABLE}_Index\" ON #{SCHEMA_NAME}.#{PARTITIONED_TABLE} (logdate, city_id)"
assert_nothing_raised { @connection.remove_index "#{SCHEMA_NAME}.#{PARTITIONED_TABLE}", name: "#{SCHEMA_NAME}.#{PARTITIONED_TABLE}_Index" }
@connection.execute "CREATE INDEX \"#{PARTITIONED_TABLE}_Index\" ON #{SCHEMA_NAME}.#{PARTITIONED_TABLE} (logdate, city_id)"
assert_raises(ArgumentError) { @connection.remove_index "#{SCHEMA2_NAME}.#{PARTITIONED_TABLE}", name: "#{SCHEMA_NAME}.#{PARTITIONED_TABLE}_Index" }
end
end
def test_primary_key_with_schema_specified
......@@ -473,6 +513,14 @@ def do_dump_index_assertions_for_one_index(this_index, this_index_name, this_ind
def bind_param(value)
ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
end
def create_partitioned_table
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{PARTITIONED_TABLE}\" (city_id integer not null, logdate date not null) PARTITION BY LIST (city_id)"
end
def create_partitioned_table_index
@connection.execute "CREATE INDEX #{PARTITIONED_TABLE_INDEX} ON #{SCHEMA_NAME}.#{PARTITIONED_TABLE} (logdate, city_id)"
end
end
class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase
......
......@@ -60,6 +60,7 @@ def supports_default_expression?
%w[
supports_savepoints?
supports_partial_index?
supports_partitioned_indexes?
supports_insert_returning?
supports_insert_on_duplicate_skip?
supports_insert_on_duplicate_update?
......
......@@ -280,6 +280,21 @@ def test_insert_all_raises_on_unknown_attribute
end
end
def test_upsert_all_works_with_partitioned_indexes
skip unless supports_insert_on_duplicate_update? && supports_insert_conflict_target? && supports_partitioned_indexes?
require "models/measurement"
Measurement.upsert_all([{ city_id: "1", logdate: 1.days.ago, peaktemp: 1, unitsales: 1 },
{ city_id: "2", logdate: 2.days.ago, peaktemp: 2, unitsales: 2 },
{ city_id: "2", logdate: 3.days.ago, peaktemp: 0, unitsales: 0 }],
unique_by: %i[logdate city_id])
assert_equal [[1.day.ago.to_date, 1, 1]],
Measurement.where(city_id: 1).pluck(:logdate, :peaktemp, :unitsales)
assert_equal [[2.days.ago.to_date, 2, 2], [3.days.ago.to_date, 0, 0]],
Measurement.where(city_id: 2).pluck(:logdate, :peaktemp, :unitsales)
end
private
def capture_log_output
output = StringIO.new
......
# frozen_string_literal: true
class Measurement < ActiveRecord::Base
end
......@@ -108,4 +108,18 @@
t.uuid :uuid, primary_key: true, **uuid_default
t.string :title
end
if supports_partitioned_indexes?
create_table(:measurements, id: false, force: true, options: "PARTITION BY LIST (city_id)") do |t|
t.string :city_id, null: false
t.date :logdate, null: false
t.integer :peaktemp
t.integer :unitsales
t.index [:logdate, :city_id], unique: true
end
create_table(:measurements_toronto, id: false, force: true,
options: "PARTITION OF measurements FOR VALUES IN (1)")
create_table(:measurements_concepcion, id: false, force: true,
options: "PARTITION OF measurements FOR VALUES IN (2)")
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册