@@ -26,7 +26,6 @@ DECLARE
26
26
pname text ;
27
27
BEGIN
28
28
IF NEW .initial_node != (SELECT shardman .get_node_id ()) THEN
29
- RAISE DEBUG ' [SHARDMAN] new table trig, pid %' , (select pg_backend_pid());
30
29
EXECUTE format(' DROP TABLE IF EXISTS %I CASCADE;' , NEW .relation );
31
30
partition_names :=
32
31
(SELECT ARRAY(SELECT part_name FROM shardman .gen_part_names (
@@ -210,12 +209,12 @@ END $$ LANGUAGE plpgsql;
210
209
211
210
-- On adding new partition, create proper foreign server & foreign table and
212
211
-- replace tmp (empty) partition with it.
213
- -- TODO: race condition between this trigger and new_table_worker_side
214
- -- definitely deserves attention.
212
+ -- TODO: There is a race condition between this trigger and
213
+ -- new_table_worker_side trigger during initial tablesync, we should deal with
214
+ -- it.
215
215
CREATE FUNCTION new_primary () RETURNS TRIGGER AS $$
216
216
BEGIN
217
217
IF NEW .owner != (SELECT shardman .get_node_id ()) THEN
218
- RAISE DEBUG ' SHARDMAN new prim trigger, pid %' , (select pg_backend_pid());
219
218
PERFORM shardman .replace_usual_part_with_foreign (NEW);
220
219
END IF;
221
220
RETURN NULL ;
@@ -297,28 +296,86 @@ $$ LANGUAGE plpgsql;
297
296
CREATE FUNCTION readonly_table_on (relation regclass)
298
297
RETURNS void AS $$
299
298
BEGIN
300
- -- Create go away trigger to prevent any new ones
299
+ -- Create go away trigger to prevent any modifications
301
300
PERFORM shardman .readonly_table_off (relation);
302
- EXECUTE format(
303
- ' CREATE TRIGGER shardman_readonly BEFORE INSERT OR UPDATE OR DELETE OR
304
- TRUNCATE ON %I FOR EACH STATEMENT EXECUTE PROCEDURE shardman.go_away();' ,
305
- relation);
306
- EXECUTE format(
307
- ' ALTER TABLE %I ENABLE ALWAYS TRIGGER shardman_readonly;' , relation);
301
+ PERFORM shardman .create_modification_triggers (relation, ' shardman_readonly' ,
302
+ ' shardman.go_away()' );
308
303
END
309
304
$$ LANGUAGE plpgsql STRICT;
310
305
CREATE FUNCTION go_away () RETURNS TRIGGER AS $$
311
306
BEGIN
312
307
RAISE EXCEPTION ' The "%" table is read only.' , TG_TABLE_NAME
313
308
USING HINT = ' Probably table copy is in progress' ;
314
- RETURN NULL ;
315
309
END;
316
310
$$ LANGUAGE plpgsql;
317
311
-- And make it writable again
318
312
CREATE FUNCTION readonly_table_off (relation regclass)
319
313
RETURNS void AS $$
320
314
BEGIN
321
315
EXECUTE format(' DROP TRIGGER IF EXISTS shardman_readonly ON %s' , relation);
316
+ EXECUTE format(' DROP TRIGGER IF EXISTS shardman_readonly_stmt ON %s' , relation);
317
+ END $$ LANGUAGE plpgsql STRICT;
318
+
319
+ -- Make replica read-only, i.e. readonly for all but LR apply workers
320
+ CREATE FUNCTION readonly_replica_on (relation regclass)
321
+ RETURNS void AS $$
322
+ BEGIN
323
+ RAISE DEBUG ' [SHARDMAN] table % made read-only for all but apply workers' , relation;
324
+ PERFORM shardman .readonly_replica_off (relation);
325
+ PERFORM shardman .create_modification_triggers (
326
+ relation, ' shardman_readonly_replica' , ' shardman.ror_go_away()' );
327
+ END $$ LANGUAGE plpgsql STRICT;
328
+ -- This function is impudent because it is used as both stmt and row trigger.
329
+ -- The idea is that we must never reach RETURN NEW after stmt row trigger,
330
+ -- because stmt trigger fires only on TRUNCATE which is impossible in LR.
331
+ -- Besides, I checked that nothing bad happens if we return NEW from stmt
332
+ -- trigger function anyway.
333
+ CREATE FUNCTION ror_go_away () RETURNS TRIGGER AS $$
334
+ BEGIN
335
+ IF NOT shardman .inside_apply_worker () THEN
336
+ RAISE EXCEPTION ' The "%" table is read only for non-apply workers' , TG_TABLE_NAME
337
+ USING HINT =
338
+ ' If you see this, most probably node with primary part has failed and' ||
339
+ ' you need to promote replica. Promotion is not yet implemented, sorry :(' ;
340
+ END IF;
341
+ raise warning ' NEW IS %' , NEW;
342
+ RETURN NEW;
343
+ END $$ LANGUAGE plpgsql;
344
+ -- And make replica writable again
345
+ CREATE FUNCTION readonly_replica_off (relation regclass) RETURNS void AS $$
346
+ BEGIN
347
+ EXECUTE format(' DROP TRIGGER IF EXISTS shardman_readonly_replica ON %s' ,
348
+ relation);
349
+ EXECUTE format(' DROP TRIGGER IF EXISTS shardman_readonly_replica_stmt ON %s' ,
350
+ relation);
351
+ END $$ LANGUAGE plpgsql STRICT;
352
+ CREATE FUNCTION inside_apply_worker () RETURNS bool AS ' pg_shardman' LANGUAGE C;
353
+
354
+ -- Create two triggers firing exec_proc before any modification operation, make
355
+ -- them ALWAYS ENABLE. We need two triggers because TRUNCATE doesn't work with
356
+ -- FOR EACH ROW, while LR doesn't support STATEMENT triggers (well, there is no
357
+ -- statements in WAL) and changes may sneak through it.
358
+ -- If you are curious, we use %I to format any identifiers (e.g. quote identifier
359
+ -- with "" if it contains spaces) and use %s while formatting regclass, because
360
+ -- it quotes everything automatically while casting oid to name.
361
+ CREATE FUNCTION create_modification_triggers (
362
+ relation regclass, trigname name, exec_proc text ) RETURNS void AS $$
363
+ DECLARE
364
+ stmt_trigname text ;
365
+ BEGIN
366
+ EXECUTE format(
367
+ ' CREATE TRIGGER %I BEFORE INSERT OR UPDATE OR DELETE
368
+ ON %s FOR EACH ROW EXECUTE PROCEDURE %s;' ,
369
+ trigname, relation, exec_proc);
370
+ EXECUTE format(
371
+ ' ALTER TABLE %s ENABLE ALWAYS TRIGGER %I;' , relation::text , trigname);
372
+ stmt_trigname := format(' %s_stmt' , trigname);
373
+ EXECUTE format(
374
+ ' CREATE TRIGGER %I BEFORE
375
+ TRUNCATE ON %s FOR EACH STATEMENT EXECUTE PROCEDURE %s;' ,
376
+ stmt_trigname, relation, exec_proc);
377
+ EXECUTE format(
378
+ ' ALTER TABLE %s ENABLE ALWAYS TRIGGER %I;' , relation::text , stmt_trigname);
322
379
END $$ LANGUAGE plpgsql STRICT;
323
380
324
381
CREATE FUNCTION gen_create_table_sql (relation text , connstring text ) RETURNS text
0 commit comments