Class: Gitlab::Database::LockWritesManager

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/database/lock_writes_manager.rb

Constant Summary collapse

TRIGGER_FUNCTION_NAME =
'gitlab_schema_prevent_write'
EXPECTED_TRIGGER_RECORD_COUNT =

Triggers to block INSERT / UPDATE / DELETE Triggers on TRUNCATE are not added to the information_schema.triggers See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us

3

Instance Method Summary collapse

Constructor Details

#initialize(table_name:, connection:, database_name:, with_retries: true, logger: nil, dry_run: false, force: true) ⇒ LockWritesManager

table_name can include schema name as a prefix. For example: 'gitlab_partitions_static.events_03', otherwise, it will default to current used schema, for example 'public'.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/gitlab/database/lock_writes_manager.rb', line 15

def initialize(
  table_name:, connection:, database_name:,
  with_retries: true, logger: nil, dry_run: false, force: true
)
  @table_name = table_name.to_s
  @connection = connection
  @database_name = database_name
  @logger = logger
  @dry_run = dry_run
  @force = force
  @with_retries = with_retries

  @table_name_without_schema = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
    .extract_schema_qualified_name(table_name.to_s)
    .identifier
end

Instance Method Details

#lock_writesObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/gitlab/database/lock_writes_manager.rb', line 42

def lock_writes
  unless force
    unless table_exist?
      logger&.info "Skipping lock_writes, because #{table_name} does not exist"
      return result_hash(action: 'skipped')
    end

    if table_locked_for_writes?
      logger&.info "Skipping lock_writes, because #{table_name} is already locked for writes"
      return result_hash(action: 'skipped')
    end
  end

  logger&.info Rainbow("Database: '#{database_name}', Table: '#{table_name}': Lock Writes").yellow
  sql_statement = "    CREATE OR REPLACE TRIGGER \#{write_trigger_name}\n      BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE\n      ON \#{table_name}\n      FOR EACH STATEMENT EXECUTE FUNCTION \#{TRIGGER_FUNCTION_NAME}();\n  SQL\n\n  result = process_query(sql_statement, 'lock')\n\n  result_hash(action: result)\nrescue ActiveRecord::StatementInvalid => e\n  # Errors like Gitlab::Database::GitlabSchema::UnknownSchemaError, PG::UndefinedTable\n  # are raised under this error\n  logger&.warn \"Failed lock_writes, because \#{table_name} raised an error. Error: \#{e}\"\n  result_hash(action: 'skipped')\nend\n"

#table_locked_for_writes?Boolean

Returns:

  • (Boolean)


32
33
34
35
36
37
38
39
40
# File 'lib/gitlab/database/lock_writes_manager.rb', line 32

def table_locked_for_writes?
  query = "      SELECT COUNT(*) from information_schema.triggers\n      WHERE event_object_table = '\#{table_name_without_schema}'\n      AND trigger_name = '\#{write_trigger_name}'\n  SQL\n\n  connection.select_value(query) == EXPECTED_TRIGGER_RECORD_COUNT\nend\n"

#unlock_writesObject



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/gitlab/database/lock_writes_manager.rb', line 73

def unlock_writes
  if force || table_locked_for_writes?
    logger&.info Rainbow("Database: '#{database_name}', Table: '#{table_name}': Allow Writes").green
    sql_statement = "      DROP TRIGGER IF EXISTS \#{write_trigger_name} ON \#{table_name};\n    SQL\n\n    result = process_query(sql_statement, 'unlock')\n\n    result_hash(action: result)\n  else\n    logger&.info \"Skipping unlock_writes, because \#{table_name} is already unlocked for writes\"\n    result_hash(action: 'skipped')\n  end\nend\n"