Class: Bemi

Inherits:
Object
  • Object
show all
Defined in:
lib/bemi.rb,
lib/bemi/context.rb,
lib/bemi/version.rb,
lib/bemi/migration.rb,
lib/bemi/migration_generator.rb

Defined Under Namespace

Classes: MigrationGenerator

Constant Summary collapse

READ_QUERY =
ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
  :close, :declare, :fetch, :move, :set, :show
)
MAX_CONTEXT_SIZE =

~1MB

1_000_000
VERSION =
"0.2.0"

Class Method Summary collapse

Class Method Details

.append_contextObject



21
22
23
24
25
26
27
28
29
# File 'lib/bemi/context.rb', line 21

def append_context
  Proc.new do |sql, adapter = nil| # Adapter is automatically passed only with Rails v7.1+
    if (adapter ? adapter.write_query?(sql) : write_query?(sql)) && ctx = serialized_context
      "#{sql} /*Bemi #{ctx} Bemi*/"
    else
      sql
    end
  end
end

.contextObject



17
18
19
# File 'lib/bemi/context.rb', line 17

def context
  Thread.current[:bemi_context]
end

.migrateObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/bemi/migration.rb', line 4

def self.migrate
  %{
    CREATE OR REPLACE FUNCTION _bemi_row_trigger_func()
      RETURNS TRIGGER
    AS $$
    DECLARE
      _bemi_metadata TEXT;
    BEGIN
      SELECT split_part(split_part(current_query(), '/*Bemi ', 2), ' Bemi*/', 1) INTO _bemi_metadata;
      IF _bemi_metadata <> '' THEN
        PERFORM pg_logical_emit_message(true, '_bemi', _bemi_metadata);
      END IF;

      IF (TG_OP = 'DELETE') THEN
        RETURN OLD;
      ELSE
        RETURN NEW;
      END IF;
    END;
    $$ LANGUAGE plpgsql;

    CREATE OR REPLACE PROCEDURE _bemi_create_triggers()
    AS $$
    DECLARE
      current_tablename TEXT;
    BEGIN
      FOR current_tablename IN
        SELECT tablename FROM pg_tables WHERE schemaname = 'public'
      LOOP
        EXECUTE format(
          'CREATE OR REPLACE TRIGGER _bemi_row_trigger_%s
          BEFORE INSERT OR UPDATE OR DELETE ON %I FOR EACH ROW
          EXECUTE FUNCTION _bemi_row_trigger_func()',
          current_tablename, current_tablename
        );
      END LOOP;
    END;
    $$ LANGUAGE plpgsql;

    CALL _bemi_create_triggers();

    CREATE OR REPLACE FUNCTION _bemi_create_table_trigger_func()
      RETURNS event_trigger
    AS $$
    BEGIN
      CALL _bemi_create_triggers();
    END
    $$ LANGUAGE plpgsql;

    DO $$
    BEGIN
      DROP EVENT TRIGGER IF EXISTS _bemi_create_table_trigger;
      CREATE EVENT TRIGGER _bemi_create_table_trigger ON ddl_command_end WHEN TAG IN ('CREATE TABLE') EXECUTE FUNCTION _bemi_create_table_trigger_func();
    EXCEPTION WHEN insufficient_privilege THEN
      RAISE NOTICE 'Please execute "CALL _bemi_create_triggers();" manually after adding new tables you want to track. (%) %.', SQLSTATE, SQLERRM;
    END
    $$ LANGUAGE plpgsql;
  }
end

.rollbackObject



64
65
66
67
68
69
70
71
# File 'lib/bemi/migration.rb', line 64

def self.rollback
  %{
    DROP EVENT TRIGGER _bemi_create_table_trigger;
    DROP FUNCTION _bemi_create_table_trigger_func;
    DROP PROCEDURE _bemi_create_triggers;
    DROP FUNCTION _bemi_row_trigger_func CASCADE;
  }
end

.set_context(ctx) ⇒ Object



13
14
15
# File 'lib/bemi/context.rb', line 13

def set_context(ctx)
  Thread.current[:bemi_context] = ctx
end