diff --git a/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb index f367292f4b0a4960fe35f08021e1ddf04f91f289..0bc1343acca129ed6edf854838186000dd24efe5 100644 --- a/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb +++ b/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb @@ -32,7 +32,7 @@ def add_concurrent_partitioned_index(table_name, column_names, options = {}) return end - partitioned_table.postgres_partitions.each do |partition| + partitioned_table.postgres_partitions.order(:name).each do |partition| partition_index_name = generated_index_name(partition.identifier, options[:name]) partition_options = options.merge(name: partition_index_name) diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb index 1afd5bdb2cadd1b6f513a8275918852fc157fbfb..38ade20de2868721d8e6d00892726b23b8ae0fbf 100644 --- a/spec/factories_spec.rb +++ b/spec/factories_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'factories' do - include DatabaseHelpers + include Database::DatabaseHelpers shared_examples 'factory' do |factory| describe "#{factory.name} factory" do diff --git a/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb index 563999416628f422bcb106fe8cc0f6b7f4bdb539..ec89f2ed61c775e9fc15d21c0d4fb7437260296d 100644 --- a/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb +++ b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::Partitioning::PartitionCreator do - include PartitioningHelpers + include Database::PartitioningHelpers include ExclusiveLeaseHelpers describe '.register' do diff --git a/spec/lib/gitlab/database/partitioning/replace_table_spec.rb b/spec/lib/gitlab/database/partitioning/replace_table_spec.rb index d47666eeffd205ea9b8d9afe14a0c1a6a9559fdd..8e27797208c7a107add9aa5e115c4db2ddee9a09 100644 --- a/spec/lib/gitlab/database/partitioning/replace_table_spec.rb +++ b/spec/lib/gitlab/database/partitioning/replace_table_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::Partitioning::ReplaceTable, '#perform' do - include TableSchemaHelpers + include Database::TableSchemaHelpers subject(:replace_table) { described_class.new(original_table, replacement_table, archived_table, 'id').perform } diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb index 7d88c17c9b373d3f40f0c5fc5e1134c20d4d7245..93dbd9d7c30021d96893f9cfb604c60f91f29df6 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers do - include TriggerHelpers + include Database::TriggerHelpers let(:model) do ActiveRecord::Migration.new.extend(described_class) diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb index 7f61ff759fc8137efeaf6009727716f373c0ca1d..603f3dc41af52b89efb3c447cf38299bfcd4a8d4 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do - include TableSchemaHelpers + include Database::TableSchemaHelpers let(:migration) do ActiveRecord::Migration.new.extend(described_class) diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb index f10ff704c178b9e72a542257550a10e9b225acd5..5fdb7dd9a85f47c62344c64a104a2ae7c1576420 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do - include PartitioningHelpers - include TriggerHelpers - include TableSchemaHelpers + include Database::PartitioningHelpers + include Database::TriggerHelpers + include Database::TableSchemaHelpers let(:migration) do ActiveRecord::Migration.new.extend(described_class) diff --git a/spec/lib/gitlab/database/reindexing/index_selection_spec.rb b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb index a5e2f368f406c63599d3d096d5d67c8361a1f85e..4466679a099ec76a912c9773de5e8e3ae292a586 100644 --- a/spec/lib/gitlab/database/reindexing/index_selection_spec.rb +++ b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::Reindexing::IndexSelection do - include DatabaseHelpers + include Database::DatabaseHelpers subject { described_class.new(Gitlab::Database::PostgresIndex.all).to_a } diff --git a/spec/support/helpers/database/database_helpers.rb b/spec/support/helpers/database/database_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..b8d7ea3662f98d877af6ed07056d37e4db709e43 --- /dev/null +++ b/spec/support/helpers/database/database_helpers.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Database + module DatabaseHelpers + # In order to directly work with views using factories, + # we can swapout the view for a table of identical structure. + def swapout_view_for_table(view) + ActiveRecord::Base.connection.execute(<<~SQL) + CREATE TABLE #{view}_copy (LIKE #{view}); + DROP VIEW #{view}; + ALTER TABLE #{view}_copy RENAME TO #{view}; + SQL + end + end +end diff --git a/spec/support/helpers/database/partitioning_helpers.rb b/spec/support/helpers/database/partitioning_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..80b31fe06032595e3618aa10a2a4419a6a2564e7 --- /dev/null +++ b/spec/support/helpers/database/partitioning_helpers.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module Database + module PartitioningHelpers + def expect_table_partitioned_by(table, columns, part_type: :range) + columns_with_part_type = columns.map { |c| [part_type.to_s, c] } + actual_columns = find_partitioned_columns(table) + + expect(columns_with_part_type).to match_array(actual_columns) + end + + def expect_range_partition_of(partition_name, table_name, min_value, max_value) + definition = find_partition_definition(partition_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) + + expect(definition).not_to be_nil + expect(definition['base_table']).to eq(table_name.to_s) + expect(definition['condition']).to eq("FOR VALUES FROM (#{min_value}) TO (#{max_value})") + end + + def expect_total_partitions(table_name, count, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) + partitions = find_partitions(table_name, schema: schema) + + expect(partitions.size).to eq(count) + end + + def expect_range_partitions_for(table_name, partitions) + partitions.each do |suffix, (min_value, max_value)| + partition_name = "#{table_name}_#{suffix}" + expect_range_partition_of(partition_name, table_name, min_value, max_value) + end + + expect_total_partitions(table_name, partitions.size, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) + end + + def expect_hash_partition_of(partition_name, table_name, modulus, remainder) + definition = find_partition_definition(partition_name, schema: Gitlab::Database::STATIC_PARTITIONS_SCHEMA) + + expect(definition).not_to be_nil + expect(definition['base_table']).to eq(table_name.to_s) + expect(definition['condition']).to eq("FOR VALUES WITH (modulus #{modulus}, remainder #{remainder})") + end + + private + + def find_partitioned_columns(table) + connection.select_rows(<<~SQL) + select + case partstrat + when 'l' then 'list' + when 'r' then 'range' + when 'h' then 'hash' + end as partstrat, + cols.column_name + from ( + select partrelid, partstrat, unnest(partattrs) as col_pos + from pg_partitioned_table + ) pg_part + inner join pg_class + on pg_part.partrelid = pg_class.oid + inner join information_schema.columns cols + on cols.table_name = pg_class.relname + and cols.ordinal_position = pg_part.col_pos + where pg_class.relname = '#{table}'; + SQL + end + + def find_partition_definition(partition, schema: ) + connection.select_one(<<~SQL) + select + parent_class.relname as base_table, + pg_get_expr(pg_class.relpartbound, inhrelid) as condition + from pg_class + inner join pg_inherits i on pg_class.oid = inhrelid + inner join pg_class parent_class on parent_class.oid = inhparent + inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace + where pg_namespace.nspname = '#{schema}' + and pg_class.relname = '#{partition}' + and pg_class.relispartition + SQL + end + + def find_partitions(partition, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) + connection.select_rows(<<~SQL) + select + pg_class.relname + from pg_class + inner join pg_inherits i on pg_class.oid = inhrelid + inner join pg_class parent_class on parent_class.oid = inhparent + inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace + where pg_namespace.nspname = '#{schema}' + and parent_class.relname = '#{partition}' + and pg_class.relispartition + SQL + end + end +end diff --git a/spec/support/helpers/database/table_schema_helpers.rb b/spec/support/helpers/database/table_schema_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..854d2c94df2ba74a6f786427108dffbc4ea9adba --- /dev/null +++ b/spec/support/helpers/database/table_schema_helpers.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Database + module TableSchemaHelpers + def connection + ActiveRecord::Base.connection + end + + def expect_table_to_be_replaced(original_table:, replacement_table:, archived_table:) + original_oid = table_oid(original_table) + replacement_oid = table_oid(replacement_table) + + yield + + expect(table_oid(original_table)).to eq(replacement_oid) + expect(table_oid(archived_table)).to eq(original_oid) + expect(table_oid(replacement_table)).to be_nil + end + + def expect_index_to_exist(name, schema: nil) + expect(index_exists_by_name(name, schema: schema)).to eq(true) + end + + def expect_index_not_to_exist(name, schema: nil) + expect(index_exists_by_name(name, schema: schema)).to be_nil + end + + def expect_primary_keys_after_tables(tables, schema: nil) + tables.each do |table| + primary_key = primary_key_constraint_name(table, schema: schema) + + expect(primary_key).to eq("#{table}_pkey") + end + end + + def table_oid(name) + connection.select_value(<<~SQL) + SELECT oid + FROM pg_catalog.pg_class + WHERE relname = '#{name}' + SQL + end + + def table_type(name) + connection.select_value(<<~SQL) + SELECT + CASE class.relkind + WHEN 'r' THEN 'normal' + WHEN 'p' THEN 'partitioned' + ELSE 'other' + END as table_type + FROM pg_catalog.pg_class class + WHERE class.relname = '#{name}' + SQL + end + + def sequence_owned_by(table_name, column_name) + connection.select_value(<<~SQL) + SELECT + sequence.relname as name + FROM pg_catalog.pg_class as sequence + INNER JOIN pg_catalog.pg_depend depend + ON depend.objid = sequence.oid + INNER JOIN pg_catalog.pg_class class + ON class.oid = depend.refobjid + INNER JOIN pg_catalog.pg_attribute attribute + ON attribute.attnum = depend.refobjsubid + AND attribute.attrelid = depend.refobjid + WHERE class.relname = '#{table_name}' + AND attribute.attname = '#{column_name}' + SQL + end + + def default_expression_for(table_name, column_name) + connection.select_value(<<~SQL) + SELECT + pg_get_expr(attrdef.adbin, attrdef.adrelid) AS default_value + FROM pg_catalog.pg_attribute attribute + INNER JOIN pg_catalog.pg_attrdef attrdef + ON attribute.attrelid = attrdef.adrelid + AND attribute.attnum = attrdef.adnum + WHERE attribute.attrelid = '#{table_name}'::regclass + AND attribute.attname = '#{column_name}' + SQL + end + + def primary_key_constraint_name(table_name, schema: nil) + table_name = schema ? "#{schema}.#{table_name}" : table_name + + connection.select_value(<<~SQL) + SELECT + conname AS constraint_name + FROM pg_catalog.pg_constraint + WHERE pg_constraint.conrelid = '#{table_name}'::regclass + AND pg_constraint.contype = 'p' + SQL + end + + def index_exists_by_name(index, schema: nil) + schema = schema ? "'#{schema}'" : 'current_schema' + + connection.select_value(<<~SQL) + SELECT true + FROM pg_catalog.pg_index i + INNER JOIN pg_catalog.pg_class c + ON c.oid = i.indexrelid + INNER JOIN pg_catalog.pg_namespace n + ON c.relnamespace = n.oid + WHERE c.relname = '#{index}' + AND n.nspname = #{schema} + SQL + end + end +end diff --git a/spec/support/helpers/database/trigger_helpers.rb b/spec/support/helpers/database/trigger_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..9ec03e684138b663ae1b255ef5c4b9703cb98ddb --- /dev/null +++ b/spec/support/helpers/database/trigger_helpers.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Database + module TriggerHelpers + def expect_function_to_exist(name) + expect(find_function_def(name)).not_to be_nil + end + + def expect_function_not_to_exist(name) + expect(find_function_def(name)).to be_nil + end + + def expect_function_to_contain(name, *statements) + return_stmt, *body_stmts = parsed_function_statements(name).reverse + + expect(return_stmt).to eq('return old') + expect(body_stmts).to contain_exactly(*statements) + end + + def expect_trigger_not_to_exist(table_name, name) + expect(find_trigger_def(table_name, name)).to be_nil + end + + def expect_valid_function_trigger(table_name, name, fn_name, fires_on) + events, timing, definition = cleaned_trigger_def(table_name, name) + + events = events&.split(',') + expected_timing, expected_events = fires_on.first + expect(timing).to eq(expected_timing.to_s) + expect(events).to match_array(Array.wrap(expected_events)) + + expect(definition).to match(%r{execute (?:procedure|function) #{fn_name}()}) + end + + private + + def parsed_function_statements(name) + cleaned_definition = find_function_def(name)['body'].downcase.gsub(/\s+/, ' ') + statements = cleaned_definition.sub(/\A\s*begin\s*(.*)\s*end\s*\Z/, "\\1") + statements.split(';').map! { |stmt| stmt.strip.presence }.compact! + end + + def find_function_def(name) + connection.select_one(<<~SQL) + SELECT prosrc AS body + FROM pg_proc + WHERE proname = '#{name}' + SQL + end + + def cleaned_trigger_def(table_name, name) + find_trigger_def(table_name, name).values_at('event', 'action_timing', 'action_statement').map!(&:downcase) + end + + def find_trigger_def(table_name, name) + connection.select_one(<<~SQL) + SELECT + string_agg(event_manipulation, ',') AS event, + action_timing, + action_statement + FROM information_schema.triggers + WHERE event_object_table = '#{table_name}' + AND trigger_name = '#{name}' + GROUP BY 2, 3 + SQL + end + end +end diff --git a/spec/support/helpers/database_helpers.rb b/spec/support/helpers/database_helpers.rb deleted file mode 100644 index e9f0a74a8d1060f4b253c69691d6c17ec196c071..0000000000000000000000000000000000000000 --- a/spec/support/helpers/database_helpers.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module DatabaseHelpers - # In order to directly work with views using factories, - # we can swapout the view for a table of identical structure. - def swapout_view_for_table(view) - ActiveRecord::Base.connection.execute(<<~SQL) - CREATE TABLE #{view}_copy (LIKE #{view}); - DROP VIEW #{view}; - ALTER TABLE #{view}_copy RENAME TO #{view}; - SQL - end -end diff --git a/spec/support/helpers/partitioning_helpers.rb b/spec/support/helpers/partitioning_helpers.rb deleted file mode 100644 index 8981fea04d53795aa34c417938cd1badf5ed7c45..0000000000000000000000000000000000000000 --- a/spec/support/helpers/partitioning_helpers.rb +++ /dev/null @@ -1,94 +0,0 @@ -# frozen_string_literal: true - -module PartitioningHelpers - def expect_table_partitioned_by(table, columns, part_type: :range) - columns_with_part_type = columns.map { |c| [part_type.to_s, c] } - actual_columns = find_partitioned_columns(table) - - expect(columns_with_part_type).to match_array(actual_columns) - end - - def expect_range_partition_of(partition_name, table_name, min_value, max_value) - definition = find_partition_definition(partition_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) - - expect(definition).not_to be_nil - expect(definition['base_table']).to eq(table_name.to_s) - expect(definition['condition']).to eq("FOR VALUES FROM (#{min_value}) TO (#{max_value})") - end - - def expect_total_partitions(table_name, count, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) - partitions = find_partitions(table_name, schema: schema) - - expect(partitions.size).to eq(count) - end - - def expect_range_partitions_for(table_name, partitions) - partitions.each do |suffix, (min_value, max_value)| - partition_name = "#{table_name}_#{suffix}" - expect_range_partition_of(partition_name, table_name, min_value, max_value) - end - - expect_total_partitions(table_name, partitions.size, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) - end - - def expect_hash_partition_of(partition_name, table_name, modulus, remainder) - definition = find_partition_definition(partition_name, schema: Gitlab::Database::STATIC_PARTITIONS_SCHEMA) - - expect(definition).not_to be_nil - expect(definition['base_table']).to eq(table_name.to_s) - expect(definition['condition']).to eq("FOR VALUES WITH (modulus #{modulus}, remainder #{remainder})") - end - - private - - def find_partitioned_columns(table) - connection.select_rows(<<~SQL) - select - case partstrat - when 'l' then 'list' - when 'r' then 'range' - when 'h' then 'hash' - end as partstrat, - cols.column_name - from ( - select partrelid, partstrat, unnest(partattrs) as col_pos - from pg_partitioned_table - ) pg_part - inner join pg_class - on pg_part.partrelid = pg_class.oid - inner join information_schema.columns cols - on cols.table_name = pg_class.relname - and cols.ordinal_position = pg_part.col_pos - where pg_class.relname = '#{table}'; - SQL - end - - def find_partition_definition(partition, schema: ) - connection.select_one(<<~SQL) - select - parent_class.relname as base_table, - pg_get_expr(pg_class.relpartbound, inhrelid) as condition - from pg_class - inner join pg_inherits i on pg_class.oid = inhrelid - inner join pg_class parent_class on parent_class.oid = inhparent - inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace - where pg_namespace.nspname = '#{schema}' - and pg_class.relname = '#{partition}' - and pg_class.relispartition - SQL - end - - def find_partitions(partition, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA) - connection.select_rows(<<~SQL) - select - pg_class.relname - from pg_class - inner join pg_inherits i on pg_class.oid = inhrelid - inner join pg_class parent_class on parent_class.oid = inhparent - inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace - where pg_namespace.nspname = '#{schema}' - and parent_class.relname = '#{partition}' - and pg_class.relispartition - SQL - end -end diff --git a/spec/support/helpers/table_schema_helpers.rb b/spec/support/helpers/table_schema_helpers.rb deleted file mode 100644 index 287942111905548cef5cee5db26f8470844d8b48..0000000000000000000000000000000000000000 --- a/spec/support/helpers/table_schema_helpers.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -module TableSchemaHelpers - def connection - ActiveRecord::Base.connection - end - - def expect_table_to_be_replaced(original_table:, replacement_table:, archived_table:) - original_oid = table_oid(original_table) - replacement_oid = table_oid(replacement_table) - - yield - - expect(table_oid(original_table)).to eq(replacement_oid) - expect(table_oid(archived_table)).to eq(original_oid) - expect(table_oid(replacement_table)).to be_nil - end - - def expect_index_to_exist(name, schema: nil) - expect(index_exists_by_name(name, schema: schema)).to eq(true) - end - - def expect_index_not_to_exist(name, schema: nil) - expect(index_exists_by_name(name, schema: schema)).to be_nil - end - - def expect_primary_keys_after_tables(tables, schema: nil) - tables.each do |table| - primary_key = primary_key_constraint_name(table, schema: schema) - - expect(primary_key).to eq("#{table}_pkey") - end - end - - def table_oid(name) - connection.select_value(<<~SQL) - SELECT oid - FROM pg_catalog.pg_class - WHERE relname = '#{name}' - SQL - end - - def table_type(name) - connection.select_value(<<~SQL) - SELECT - CASE class.relkind - WHEN 'r' THEN 'normal' - WHEN 'p' THEN 'partitioned' - ELSE 'other' - END as table_type - FROM pg_catalog.pg_class class - WHERE class.relname = '#{name}' - SQL - end - - def sequence_owned_by(table_name, column_name) - connection.select_value(<<~SQL) - SELECT - sequence.relname as name - FROM pg_catalog.pg_class as sequence - INNER JOIN pg_catalog.pg_depend depend - ON depend.objid = sequence.oid - INNER JOIN pg_catalog.pg_class class - ON class.oid = depend.refobjid - INNER JOIN pg_catalog.pg_attribute attribute - ON attribute.attnum = depend.refobjsubid - AND attribute.attrelid = depend.refobjid - WHERE class.relname = '#{table_name}' - AND attribute.attname = '#{column_name}' - SQL - end - - def default_expression_for(table_name, column_name) - connection.select_value(<<~SQL) - SELECT - pg_get_expr(attrdef.adbin, attrdef.adrelid) AS default_value - FROM pg_catalog.pg_attribute attribute - INNER JOIN pg_catalog.pg_attrdef attrdef - ON attribute.attrelid = attrdef.adrelid - AND attribute.attnum = attrdef.adnum - WHERE attribute.attrelid = '#{table_name}'::regclass - AND attribute.attname = '#{column_name}' - SQL - end - - def primary_key_constraint_name(table_name, schema: nil) - table_name = schema ? "#{schema}.#{table_name}" : table_name - - connection.select_value(<<~SQL) - SELECT - conname AS constraint_name - FROM pg_catalog.pg_constraint - WHERE pg_constraint.conrelid = '#{table_name}'::regclass - AND pg_constraint.contype = 'p' - SQL - end - - def index_exists_by_name(index, schema: nil) - schema = schema ? "'#{schema}'" : 'current_schema' - - connection.select_value(<<~SQL) - SELECT true - FROM pg_catalog.pg_index i - INNER JOIN pg_catalog.pg_class c - ON c.oid = i.indexrelid - INNER JOIN pg_catalog.pg_namespace n - ON c.relnamespace = n.oid - WHERE c.relname = '#{index}' - AND n.nspname = #{schema} - SQL - end -end diff --git a/spec/support/helpers/trigger_helpers.rb b/spec/support/helpers/trigger_helpers.rb deleted file mode 100644 index dd6d8ff5bb56fe70424ded46c143640a440e1a41..0000000000000000000000000000000000000000 --- a/spec/support/helpers/trigger_helpers.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module TriggerHelpers - def expect_function_to_exist(name) - expect(find_function_def(name)).not_to be_nil - end - - def expect_function_not_to_exist(name) - expect(find_function_def(name)).to be_nil - end - - def expect_function_to_contain(name, *statements) - return_stmt, *body_stmts = parsed_function_statements(name).reverse - - expect(return_stmt).to eq('return old') - expect(body_stmts).to contain_exactly(*statements) - end - - def expect_trigger_not_to_exist(table_name, name) - expect(find_trigger_def(table_name, name)).to be_nil - end - - def expect_valid_function_trigger(table_name, name, fn_name, fires_on) - events, timing, definition = cleaned_trigger_def(table_name, name) - - events = events&.split(',') - expected_timing, expected_events = fires_on.first - expect(timing).to eq(expected_timing.to_s) - expect(events).to match_array(Array.wrap(expected_events)) - - expect(definition).to match(%r{execute (?:procedure|function) #{fn_name}()}) - end - - private - - def parsed_function_statements(name) - cleaned_definition = find_function_def(name)['body'].downcase.gsub(/\s+/, ' ') - statements = cleaned_definition.sub(/\A\s*begin\s*(.*)\s*end\s*\Z/, "\\1") - statements.split(';').map! { |stmt| stmt.strip.presence }.compact! - end - - def find_function_def(name) - connection.select_one(<<~SQL) - SELECT prosrc AS body - FROM pg_proc - WHERE proname = '#{name}' - SQL - end - - def cleaned_trigger_def(table_name, name) - find_trigger_def(table_name, name).values_at('event', 'action_timing', 'action_statement').map!(&:downcase) - end - - def find_trigger_def(table_name, name) - connection.select_one(<<~SQL) - SELECT - string_agg(event_manipulation, ',') AS event, - action_timing, - action_statement - FROM information_schema.triggers - WHERE event_object_table = '#{table_name}' - AND trigger_name = '#{name}' - GROUP BY 2, 3 - SQL - end -end