module Postspec class Render attr_reader :postspec forward_to :postspec, :conn SEED_BUD_TRIGGER_NAME = "postspec_readonly_bud_trg" SEED_BT_TRIGGER_NAME = "postspec_readonly_bt_trg" def initialize(postspec) constrain postspec, Postspec @postspec = postspec end def truncate_tables(uids) ["truncate #{uids.join(', ')} cascade"] end def delete_tables(uids) uids.map { |uid| "delete from #{uid}" } end # FIXME DUPLICATED def reset_postspec_tables() delete_tables %w(postspec.runs postspec.seeds postspec.inserts postspec.updates postspec.deletes) end def postspec_schema(state) raise NotYet end def change_triggers(state) constrain state, lambda { |state| [:create, :drop].include?(state) } postspec.tables.map { |table| %w(insert update delete).map { |event| name = "register_#{event}_b#{event[0]}_trg" exist = postspec.meta.exist?("#{table.uid}.#{name}()") if state == :create && !exist ref = (event == "insert" ? "new" : "old") <<~EOS create trigger #{name} before #{event} on #{table.uid} for each row execute function postspec.register_#{event}() EOS elsif state == :drop && exist "drop trigger if exists #{name} on #{table.uid}" else nil end }.compact }.flatten end def seed_triggers(state, uids = nil) case state when :create; create_seed_triggers(uids) when :drop; drop_seed_triggers else raise ArgumentError end end def drop_seed_triggers postspec.tables.map { |uid| [SEED_BUD_TRIGGER_NAME, SEED_BT_TRIGGER_NAME].map { |trigger| trigger_uid = "#{uid}.#{trigger}()" postspec.meta.exist?(trigger_uid) ? "drop trigger #{trigger} on #{uid}" : nil }.compact }.flatten end # Create readonly seed triggers. Readonly triggers are used to raise an # error when seed data are updated, deleted, or truncated. They all call # the common postspec.readonly_failure() function that raises a Postgres # exception def create_seed_triggers(uids) constrain uids, String => [Integer, NilClass] result = [] uids.map { |uid, id| bud_trigger = "#{uid}.#{SEED_BUD_TRIGGER_NAME}()" bud_sql = <<~EOS1 create trigger #{SEED_BUD_TRIGGER_NAME} before update or delete on #{uid} for each row when (old.id <= #{id}) execute function postspec.readonly_failure('#{uid}') EOS1 bt_sql = <<~EOS2 create trigger postspec_readonly_trigger_bt before truncate on #{uid} execute function postspec.readonly_failure('#{uid}') EOS2 [bud_sql, bt_sql, "insert into postspec.seeds (table_uid, record_id) values ('#{uid}', #{id})"] }.flatten end def execution_unit(tables, sql) return [] if sql.empty? materialized_views = tables.select { |uid| uid !~ /^postspec\./ }.map { |uid| postspec.type.dot(uid).depending_materialized_views }.flatten.map(&:uid).uniq sql = tables.map { |uid| "alter table #{uid} disable trigger all" } + sql + tables.map { |uid| "alter table #{uid} enable trigger all" } + materialized_views.map { |uid| "refresh materialized view #{uid}" } end def delete_tables(arg) constrain arg, Array, Hash uids = arg.is_a?(Array) ? arg.map { |uid| [uid, 0] }.to_h : arg sql = uids.map { |uid, id| "delete from #{uid}" + (id > 0 ? " where id > #{id}" : "") } + uids.select { |uid| uid =~ /^postspec\./ ? true : !postspec.type.dot(uid).sub_table? }.map { |uid, id| "alter table #{uid} alter column id restart" + (id > 0 ? " with #{id+1}" : "") } end # FIXME: doesn't seem to be any improvement performance-wise def delete_tables_new(arg) constrain arg, Array, Hash if arg.is_a?(Array) delete_all = arg delete_only = {} else delete_all = [] delete_only = {} arg.each { |uid, id| if id == 0 delete_all << uid else delete_only[uid] = id end } end table_alias_index = 0 if delete_all.empty? delete_all_sql = [] else delete_all_sql = [ "with " + delete_all.map { |uid| "t#{table_alias_index += 1} as (delete from #{uid} returning 1 as id)" }.join(", ") + " select " + (1...table_alias_index).map { |i| "t#{i}.id" }.join(", ") + " from " + (1...table_alias_index).map { |i| "t#{i}" }.join(", ") ] + delete_all.map { |uid| "alter table #{uid} alter column id restart" } end delete_only_sql = delete_only.map { |uid, id| "delete from #{uid}" + (id > 0 ? " > #{id}" : "") } + delete_only.map { |uid, id| "alter table #{uid} alter column id restart with #{id+1}" } sql = delete_all_sql + delete_only_sql # uids.map { |uid, id| "delete from #{uid}" + (id > 0 ? " > #{id}" : "") } + # uids.map { |uid, id| "alter table #{uid} alter column id restart" } end end end