-- ===================================================================== -- LogShip template cleanup — iDempiere DB truncation -- --------------------------------------------------------------------- -- Run ONLY on a clone (never production!). The wrapper run-cleanup.sh -- enforces a hostname/IP guard before invoking this file. -- -- Semantics (as decided for the "bare template"): -- * Client 1000000 (logyou) is kept, orgs 0 + 1000000 are kept. -- * ALL transactional data of client 1000000 is deleted. -- * ALL business partners / products of client 1000000 are deleted, -- EXCEPT rows required by config — enforced naturally: any row still -- referenced by kept config survives via FK protection (row-wise -- delete fallback skips rows whose DELETE raises an FK violation). -- * All orgs of client 1000000 except 1000000 are purged from every -- table that has an ad_org_id column (dynamic). -- * GardenWorld demo client (11) and System (0) are untouched, except -- pure log tables which are cleared globally. -- -- Engine: cleanup_target list processed in pass order. Per pass: -- Stage 1: bulk DELETEs repeated until no progress (FK errors skipped) -- Stage 2: bulk DELETE with row-wise ctid fallback (FK-blocked rows -- survive and are counted) repeated until no progress. -- Every target is guarded with to_regclass, so listing tables that don't -- exist in this DB is harmless. -- -- Usage: psql -U postgres -d idempiere -f 01-idempiere-truncate.sql -- (VACUUM FULL runs separately from run-cleanup.sh — not allowed in tx.) -- ===================================================================== \set ON_ERROR_STOP on BEGIN; SET LOCAL search_path TO adempiere, public; -- iDempiere creates some FKs DEFERRABLE; force them to check per-statement so -- (a) the FK-retry engine sees their violations like any other FK and -- (b) no pending trigger events remain when triggers are re-enabled below. SET CONSTRAINTS ALL IMMEDIATE; -- --------------------------------------------------------------------- -- Disable the Elasticsearch-sync triggers (functions log_*_changes) for -- the duration of the run. Reasons: (a) log_product_changes() reads -- NEW.m_product_id even on DELETE -> NOT NULL violation on es_sync_queue, -- aborting the whole run; (b) every delete would pointlessly enqueue -- sync events into a queue we empty anyway. Re-enabled before COMMIT; -- a rollback also restores them (ALTER TABLE is transactional). -- --------------------------------------------------------------------- CREATE TEMP TABLE disabled_triggers (tbl text, trg text) ON COMMIT DROP; DO $$ DECLARE r record; BEGIN FOR r IN SELECT c.relname AS tbl, t.tgname AS trg FROM pg_trigger t JOIN pg_class c ON c.oid = t.tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_proc p ON p.oid = t.tgfoid WHERE n.nspname = 'adempiere' AND NOT t.tgisinternal AND p.proname LIKE 'log\_%\_changes' LOOP EXECUTE format('ALTER TABLE adempiere.%I DISABLE TRIGGER %I', r.tbl, r.trg); INSERT INTO disabled_triggers VALUES (r.tbl, r.trg); RAISE NOTICE 'disabled ES-sync trigger % on %', r.trg, r.tbl; END LOOP; END $$; -- --------------------------------------------------------------------- -- Break self-/mutual references between documents to be deleted. -- Reversal pairs (original <-> reversal doc) reference EACH OTHER, so -- row-wise deletion can never remove either row. NULLing the link -- columns first makes the pairs deletable. Runs with ES triggers off. -- --------------------------------------------------------------------- DO $$ DECLARE pair record; n bigint; BEGIN FOR pair IN SELECT * FROM (VALUES ('m_inout', 'reversal_id'), ('m_inout', 'ref_inout_id'), ('c_invoice', 'reversal_id'), ('c_invoice', 'ref_invoice_id'), ('c_order', 'ref_order_id'), ('c_order', 'link_order_id'), ('c_payment', 'reversal_id'), ('c_payment', 'ref_payment_id'), -- invoice<->payment cross-cycle: both sides reference each other ('c_invoice', 'c_payment_id'), ('c_payment', 'c_invoice_id'), ('m_inventory', 'reversal_id'), ('m_movement', 'reversal_id'), ('m_production', 'reversal_id'), ('c_allocationhdr','reversal_id'), ('gl_journal', 'reversal_id') ) v(tbl, col) LOOP IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='adempiere' AND table_name=pair.tbl AND column_name=pair.col) THEN EXECUTE format('UPDATE adempiere.%I SET %I = NULL WHERE ad_client_id = 1000000 AND %I IS NOT NULL', pair.tbl, pair.col, pair.col); GET DIAGNOSTICS n = ROW_COUNT; IF n > 0 THEN RAISE NOTICE 'unlinked %.% on % rows', pair.tbl, pair.col, n; END IF; END IF; END LOOP; -- org <-> partner cycle: ad_org.c_bpartner_id (custom linked-partner column) -- points at partners owned by that same org. Unlink for the orgs being -- deleted only — org 1000000 keeps its link (it is config, see keep_bp). IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='adempiere' AND table_name='ad_org' AND column_name='c_bpartner_id') THEN EXECUTE 'UPDATE adempiere.ad_org SET c_bpartner_id = NULL WHERE ad_client_id = 1000000 AND ad_org_id <> 1000000 AND c_bpartner_id IS NOT NULL'; GET DIAGNOSTICS n = ROW_COUNT; IF n > 0 THEN RAISE NOTICE 'unlinked ad_org.c_bpartner_id on % deleted-org rows', n; END IF; END IF; END $$; -- --------------------------------------------------------------------- -- Protected sets: rows that must survive because runtime config points -- at them (defense in depth on top of FK protection). -- --------------------------------------------------------------------- CREATE TEMP TABLE keep_bp (c_bpartner_id numeric PRIMARY KEY) ON COMMIT DROP; CREATE TEMP TABLE keep_prod (m_product_id numeric PRIMARY KEY) ON COMMIT DROP; DO $$ BEGIN -- Custom column ad_org.c_bpartner_id (org-linked partner) — LogShip custom IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='adempiere' AND table_name='ad_org' AND column_name='c_bpartner_id') THEN EXECUTE 'INSERT INTO keep_bp SELECT DISTINCT c_bpartner_id FROM adempiere.ad_org WHERE ad_org_id IN (0,1000000) AND c_bpartner_id IS NOT NULL ON CONFLICT DO NOTHING'; END IF; -- Template/cash-trx partner from client info IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='adempiere' AND table_name='ad_clientinfo' AND column_name='c_bpartnercashtrx_id') THEN EXECUTE 'INSERT INTO keep_bp SELECT DISTINCT c_bpartnercashtrx_id FROM adempiere.ad_clientinfo WHERE ad_client_id=1000000 AND c_bpartnercashtrx_id IS NOT NULL ON CONFLICT DO NOTHING'; END IF; -- Freight product from client info IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='adempiere' AND table_name='ad_clientinfo' AND column_name='m_productfreight_id') THEN EXECUTE 'INSERT INTO keep_prod SELECT DISTINCT m_productfreight_id FROM adempiere.ad_clientinfo WHERE ad_client_id=1000000 AND m_productfreight_id IS NOT NULL ON CONFLICT DO NOTHING'; END IF; END $$; -- --------------------------------------------------------------------- -- Target list -- --------------------------------------------------------------------- CREATE TEMP TABLE cleanup_target ( id serial PRIMARY KEY, pass int NOT NULL, tbl text NOT NULL, cond text NOT NULL, done boolean NOT NULL DEFAULT false, survivors bigint ) ON COMMIT DROP; -- ============================ PASS 1 ================================= -- Transactional wipe for client 1000000 + global log tables. -- Order: custom/child tables first, then documents, then logs. INSERT INTO cleanup_target (pass, tbl, cond) VALUES -- custom transactional / partner-linked tables (1,'cust_fulfillmentfeeline', 'ad_client_id=1000000'), (1,'cust_commissionactivity', 'ad_client_id=1000000'), (1,'cust_storage_usage', 'ad_client_id=1000000'), (1,'cust_storage_usage_flat', 'ad_client_id=1000000'), (1,'cust_paket_accounting', 'ad_client_id=1000000'), (1,'cust_incominginvoice', 'ad_client_id=1000000'), (1,'cust_crossdockalloc', 'ad_client_id=1000000'), (1,'cust_invoicecreationnotes', 'ad_client_id=1000000'), (1,'cust_agreement_cbartner', 'ad_client_id=1000000'), (1,'cust_vendorinvoiceprofile', 'ad_client_id=1000000'), (1,'cust_shippingcost_bpoverride', 'ad_client_id=1000000'), (1,'cust_fulfillmentproductpricingrules','ad_client_id=1000000'), -- accounting facts / matching (1,'fact_acct', 'ad_client_id=1000000'), (1,'fact_acct_summary', 'ad_client_id=1000000'), (1,'fact_reconciliation', 'ad_client_id=1000000'), (1,'m_matchinv', 'ad_client_id=1000000'), (1,'m_matchpo', 'ad_client_id=1000000'), -- GL journals (1,'gl_journalline', 'ad_client_id=1000000'), (1,'gl_journal', 'ad_client_id=1000000'), (1,'gl_journalbatch', 'ad_client_id=1000000'), -- payments / bank (1,'c_allocationline', 'ad_client_id=1000000'), (1,'c_allocationhdr', 'ad_client_id=1000000'), (1,'c_paymentallocate', 'ad_client_id=1000000'), (1,'c_payment', 'ad_client_id=1000000'), (1,'c_paymenttransaction', 'ad_client_id=1000000'), (1,'c_bankstatementline', 'ad_client_id=1000000'), (1,'c_bankstatement', 'ad_client_id=1000000'), (1,'c_depositbatchline', 'ad_client_id=1000000'), (1,'c_depositbatch', 'ad_client_id=1000000'), (1,'c_cashline', 'ad_client_id=1000000'), (1,'c_cash', 'ad_client_id=1000000'), -- dunning runs (1,'c_dunningrunline', 'ad_client_id=1000000'), (1,'c_dunningrunentry', 'ad_client_id=1000000'), (1,'c_dunningrun', 'ad_client_id=1000000'), -- invoices (1,'c_invoiceline', 'ad_client_id=1000000'), (1,'c_invoicetax', 'ad_client_id=1000000'), (1,'c_invoicepayschedule', 'ad_client_id=1000000'), (1,'c_invoicebatchline', 'ad_client_id=1000000'), (1,'c_invoicebatch', 'ad_client_id=1000000'), (1,'c_invoice', 'ad_client_id=1000000'), -- shipments / packages (1,'m_packageline', 'ad_client_id=1000000'), (1,'m_packagembl', 'ad_client_id=1000000'), (1,'m_package', 'ad_client_id=1000000'), (1,'m_inoutlineconfirm', 'ad_client_id=1000000'), (1,'m_inoutconfirm', 'ad_client_id=1000000'), (1,'m_inoutline', 'ad_client_id=1000000'), (1,'m_inout', 'ad_client_id=1000000'), -- orders (1,'c_orderlandedcostallocation','ad_client_id=1000000'), (1,'c_orderlandedcost', 'ad_client_id=1000000'), (1,'c_orderline', 'ad_client_id=1000000'), (1,'c_ordertax', 'ad_client_id=1000000'), (1,'c_orderpayschedule', 'ad_client_id=1000000'), (1,'c_order', 'ad_client_id=1000000'), -- distribution orders (1,'dd_orderline', 'ad_client_id=1000000'), (1,'dd_order', 'ad_client_id=1000000'), -- inventory / movements / production / RMA / requisitions (1,'m_inventoryline', 'ad_client_id=1000000'), (1,'m_inventory', 'ad_client_id=1000000'), (1,'m_movementlineconfirm','ad_client_id=1000000'), (1,'m_movementconfirm', 'ad_client_id=1000000'), (1,'m_movementline', 'ad_client_id=1000000'), (1,'m_movement', 'ad_client_id=1000000'), (1,'m_productionline', 'ad_client_id=1000000'), (1,'m_productionplan', 'ad_client_id=1000000'), (1,'m_production', 'ad_client_id=1000000'), (1,'m_rmaline', 'ad_client_id=1000000'), (1,'m_rmatax', 'ad_client_id=1000000'), (1,'m_rma', 'ad_client_id=1000000'), (1,'m_requisitionline', 'ad_client_id=1000000'), (1,'m_requisition', 'ad_client_id=1000000'), -- stock / costs (1,'m_storageonhand', 'ad_client_id=1000000'), (1,'m_storagereservation', 'ad_client_id=1000000'), (1,'m_storagereservationlog','ad_client_id=1000000'), (1,'m_transaction', 'ad_client_id=1000000'), (1,'m_costdetail', 'ad_client_id=1000000'), (1,'m_costhistory', 'ad_client_id=1000000'), (1,'m_costqueue', 'ad_client_id=1000000'), (1,'m_cost', 'ad_client_id=1000000'), -- commissions (1,'c_commissiondetail', 'ad_client_id=1000000'), (1,'c_commissionamt', 'ad_client_id=1000000'), (1,'c_commissionrun', 'ad_client_id=1000000'), -- requests / tickets (1,'r_requestupdate', 'ad_client_id=1000000'), (1,'r_requestaction', 'ad_client_id=1000000'), (1,'r_request', 'ad_client_id=1000000'), -- projects (transaction side; project master falls in pass 2) (1,'c_projectissue', 'ad_client_id=1000000'), (1,'c_projectline', 'ad_client_id=1000000'), -- global logs / infrastructure noise (all clients — pure logs) (1,'ad_changelog', 'true'), (1,'ad_pinstance_log', 'true'), (1,'ad_pinstance_para', 'true'), (1,'ad_wf_eventaudit', 'true'), (1,'ad_wf_activityresult', 'true'), (1,'ad_wf_activity', 'true'), (1,'ad_wf_processdata', 'true'), (1,'ad_wf_process', 'true'), (1,'ad_pinstance', 'true'), (1,'ad_session', 'true'), (1,'ad_note', 'true'), (1,'ad_archive', 'true'), (1,'ad_attachment', 'true'), (1,'ad_issue', 'true'), (1,'ad_schedulerlog', 'true'), (1,'ad_alertprocessorlog', 'true'), (1,'ad_workflowprocessorlog', 'true'), (1,'ad_acctprocessorlog', 'true'), (1,'ad_recentitem', 'true'), (1,'ad_password_history', 'true'), (1,'rest_authtoken', 'true'), (1,'rest_refreshtoken', 'true'), (1,'es_sync_queue', 'true'), (1,'t_aging', 'true'), (1,'t_inventoryvalue', 'true'), (1,'t_invoicegl', 'true'), (1,'t_replenish', 'true'), (1,'t_reportstatement', 'true'), (1,'t_report', 'true'), (1,'t_spool', 'true'), (1,'t_transaction', 'true'), (1,'t_trialbalance', 'true'); -- ============================ PASS 2 ================================= -- Bare master data for client 1000000. FK-protected rows survive. INSERT INTO cleanup_target (pass, tbl, cond) VALUES -- product children (2,'m_productprice', 'ad_client_id=1000000 AND m_product_id NOT IN (SELECT m_product_id FROM keep_prod)'), (2,'m_productpricevendorbreak', 'ad_client_id=1000000'), (2,'m_product_po', 'ad_client_id=1000000'), (2,'m_replenish', 'ad_client_id=1000000'), (2,'m_substitute', 'ad_client_id=1000000'), (2,'m_relatedproduct', 'ad_client_id=1000000'), (2,'m_product_trl', 'ad_client_id=1000000'), (2,'m_product_acct', 'ad_client_id=1000000 AND m_product_id NOT IN (SELECT m_product_id FROM keep_prod)'), (2,'m_productdownload', 'ad_client_id=1000000'), (2,'c_bpartner_product', 'ad_client_id=1000000'), (2,'pp_product_bomline_trl', 'ad_client_id=1000000'), (2,'pp_product_bom_trl', 'ad_client_id=1000000'), (2,'pp_product_bomline', 'ad_client_id=1000000'), (2,'pp_product_bom', 'ad_client_id=1000000'), -- m_product_bom deliberately absent: it is a legacy VIEW in iDempiere 10+ -- products (2,'m_product', 'ad_client_id=1000000 AND m_product_id NOT IN (SELECT m_product_id FROM keep_prod)'), -- attribute set instances (only unreferenced ones will actually delete) (2,'m_attributeinstance', 'ad_client_id=1000000'), (2,'m_attributesetinstance', 'ad_client_id=1000000'), -- assets (2,'a_depreciation_exp', 'ad_client_id=1000000'), (2,'a_asset_acct', 'ad_client_id=1000000'), (2,'a_asset_addition', 'ad_client_id=1000000'), (2,'a_asset', 'ad_client_id=1000000'), -- projects master (2,'c_projecttask', 'ad_client_id=1000000'), (2,'c_projectphase', 'ad_client_id=1000000'), (2,'c_project', 'ad_client_id=1000000'), -- users of deletable partners: role/access rows first, then the users (2,'ad_tree_favorite_node', 'ad_tree_favorite_id IN (SELECT ad_tree_favorite_id FROM adempiere.ad_tree_favorite WHERE ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND ((c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)) OR ad_org_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000))))'), (2,'ad_tree_favorite', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND ((c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)) OR ad_org_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000)))'), (2,'ad_wlistbox_customization', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND ((c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)) OR ad_org_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000)))'), (2,'ad_user_roles', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp))'), (2,'ad_user_orgaccess', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp))'), (2,'ad_user_substitute', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp))'), (2,'ad_userbpaccess', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp))'), (2,'ad_userpreference', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp))'), (2,'ad_preference', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND ((c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)) OR ad_org_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000)))'), (2,'r_contactinterest', 'ad_user_id IN (SELECT ad_user_id FROM adempiere.ad_user WHERE ad_client_id=1000000 AND c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp))'), (2,'ad_user', 'ad_client_id=1000000 AND c_bpartner_id IS NOT NULL AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), -- partner children (2,'c_bp_bankaccount', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), (2,'c_bp_relation', 'ad_client_id=1000000'), (2,'c_bp_withholding', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), (2,'c_bp_shippingacct', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), (2,'c_bp_customer_acct', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), (2,'c_bp_vendor_acct', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), (2,'c_bp_employee_acct', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), (2,'c_bpartner_location', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), -- partners (2,'c_bpartner', 'ad_client_id=1000000 AND c_bpartner_id NOT IN (SELECT c_bpartner_id FROM keep_bp)'), -- orphaned addresses (referenced ones survive row-wise) (2,'c_location', 'ad_client_id=1000000'); -- ============================ PASS 3 ================================= -- Org purge: every table with an ad_org_id column, for all client-1000000 -- orgs except 1000000. ad_org itself is excluded here (pass 4). INSERT INTO cleanup_target (pass, tbl, cond) SELECT 3, t.tablename, 'ad_org_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000)' FROM pg_tables t JOIN information_schema.columns c ON c.table_schema='adempiere' AND c.table_name=t.tablename AND c.column_name='ad_org_id' WHERE t.schemaname='adempiere' AND t.tablename NOT IN ('ad_org'); -- Role children: access/assignment rows (usually stored with AD_Org_ID=0, -- so the dynamic purge above misses them) referencing roles that belong to -- the deleted orgs. Every table with an ad_role_id column, except ad_role. INSERT INTO cleanup_target (pass, tbl, cond) SELECT 3, t.tablename, 'ad_role_id IN (SELECT ad_role_id FROM adempiere.ad_role WHERE ad_client_id=1000000 AND ad_org_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000))' FROM pg_tables t JOIN information_schema.columns c ON c.table_schema='adempiere' AND c.table_name=t.tablename AND c.column_name='ad_role_id' WHERE t.schemaname='adempiere' AND t.tablename NOT IN ('ad_role'); -- Partners linked to deleted orgs (custom linkage column), org info, orgs INSERT INTO cleanup_target (pass, tbl, cond) SELECT 4, 'c_bpartner', 'ad_orgbp_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000)' WHERE EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='adempiere' AND table_name='c_bpartner' AND column_name='ad_orgbp_id'); INSERT INTO cleanup_target (pass, tbl, cond) VALUES (4,'ad_orginfo', 'ad_org_id IN (SELECT ad_org_id FROM adempiere.ad_org WHERE ad_client_id=1000000 AND ad_org_id<>1000000)'), (4,'ad_org', 'ad_client_id=1000000 AND ad_org_id<>1000000'), -- final sweep in case anything re-enqueued sync events mid-run (4,'es_sync_queue', 'true'); -- --------------------------------------------------------------------- -- Engine -- --------------------------------------------------------------------- DO $$ DECLARE p int; t record; r record; n bigint; pass_deleted bigint; iter int; row_deleted bigint; skipped bigint; stage int; BEGIN FOR p IN SELECT DISTINCT pass FROM cleanup_target ORDER BY pass LOOP RAISE NOTICE '=== pass % ===', p; -- drop targets that are not real tables in this DB (missing, or a VIEW -- like the legacy m_product_bom — DELETE on a view would abort the run) UPDATE cleanup_target ct SET done = true, survivors = 0 WHERE ct.pass = p AND NOT EXISTS ( SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'adempiere' AND c.relname = ct.tbl AND c.relkind IN ('r','p') ); FOR stage IN 1..2 LOOP -- stage 1: bulk only; stage 2: with row-wise fallback iter := 0; LOOP iter := iter + 1; IF iter > 100 THEN RAISE EXCEPTION 'pass % stage % did not converge after 100 iterations', p, stage; END IF; pass_deleted := 0; FOR t IN SELECT * FROM cleanup_target WHERE pass = p AND NOT done ORDER BY id LOOP BEGIN EXECUTE format('DELETE FROM adempiere.%I WHERE %s', t.tbl, t.cond); GET DIAGNOSTICS n = ROW_COUNT; pass_deleted := pass_deleted + n; UPDATE cleanup_target SET done = true, survivors = 0 WHERE id = t.id; IF n > 0 THEN RAISE NOTICE ' % : deleted % rows', t.tbl, n; END IF; EXCEPTION WHEN foreign_key_violation THEN IF stage = 1 THEN CONTINUE; -- retry in a later iteration / stage 2 END IF; -- stage 2: row-wise fallback, FK-blocked rows survive row_deleted := 0; skipped := 0; FOR r IN EXECUTE format('SELECT ctid AS rid FROM adempiere.%I WHERE %s', t.tbl, t.cond) LOOP BEGIN EXECUTE format('DELETE FROM adempiere.%I WHERE ctid = $1', t.tbl) USING r.rid; row_deleted := row_deleted + 1; EXCEPTION WHEN foreign_key_violation THEN skipped := skipped + 1; END; END LOOP; pass_deleted := pass_deleted + row_deleted; IF skipped = 0 THEN UPDATE cleanup_target SET done = true, survivors = 0 WHERE id = t.id; ELSE UPDATE cleanup_target SET survivors = skipped WHERE id = t.id; END IF; RAISE NOTICE ' % : row-wise deleted %, FK-protected survivors %', t.tbl, row_deleted, skipped; END; END LOOP; EXIT WHEN pass_deleted = 0; END LOOP; END LOOP; END LOOP; END $$; -- --------------------------------------------------------------------- -- Re-enable the ES-sync triggers disabled at the start. -- --------------------------------------------------------------------- SET CONSTRAINTS ALL IMMEDIATE; -- flush any pending deferred checks first DO $$ DECLARE r record; BEGIN FOR r IN SELECT tbl, trg FROM disabled_triggers LOOP EXECUTE format('ALTER TABLE adempiere.%I ENABLE TRIGGER %I', r.tbl, r.trg); END LOOP; RAISE NOTICE 're-enabled % ES-sync triggers', (SELECT count(*) FROM disabled_triggers); END $$; -- --------------------------------------------------------------------- -- Recreate the AD_Session row referenced by the long-lived SuperUser -- IDEMPIERETOKEN in the frontend .env (AD_Session_ID 1000081). The REST -- servlet 500s on a token whose session row is gone — without this, -- every clone needs the row re-inserted before server-side system calls -- work. Claims in the token: user 100, role 1000000, client 1000000, org 0. -- --------------------------------------------------------------------- INSERT INTO adempiere.ad_session (ad_session_id, ad_client_id, ad_org_id, isactive, created, createdby, updated, updatedby, websession, remote_addr, remote_host, processed, description, ad_role_id, logindate, ad_session_uu, servername) SELECT 1000081, 1000000, 0, 'Y', now(), 100, now(), 100, 'REST', '127.0.0.1', 'localhost', 'N', 'Recreated for IDEMPIERETOKEN (template cleanup)', 1000000, now(), gen_random_uuid()::text, 'template' WHERE NOT EXISTS (SELECT 1 FROM adempiere.ad_session WHERE ad_session_id = 1000081); -- --------------------------------------------------------------------- -- Reset DocumentNo sequences for the kept client (record-ID sequences -- with istableid='Y' must NOT be touched). -- --------------------------------------------------------------------- UPDATE adempiere.ad_sequence SET currentnext = startno WHERE ad_client_id = 1000000 AND istableid = 'N' AND name LIKE 'DocumentNo%'; -- --------------------------------------------------------------------- -- Report (printed by psql before commit) -- --------------------------------------------------------------------- SELECT 'FK-protected survivors' AS report, tbl, cond, survivors FROM cleanup_target WHERE COALESCE(survivors,0) > 0 ORDER BY survivors DESC; SELECT 'remaining orgs (client 1000000)' AS report, ad_org_id, name FROM adempiere.ad_org WHERE ad_client_id = 1000000 ORDER BY ad_org_id; SELECT 'zero checks' AS report, x.tbl, x.cnt FROM ( SELECT 'c_order' AS tbl, count(*) AS cnt FROM adempiere.c_order WHERE ad_client_id=1000000 UNION ALL SELECT 'c_invoice', count(*) FROM adempiere.c_invoice WHERE ad_client_id=1000000 UNION ALL SELECT 'm_inout', count(*) FROM adempiere.m_inout WHERE ad_client_id=1000000 UNION ALL SELECT 'fact_acct', count(*) FROM adempiere.fact_acct WHERE ad_client_id=1000000 UNION ALL SELECT 'm_storageonhand', count(*) FROM adempiere.m_storageonhand WHERE ad_client_id=1000000 UNION ALL SELECT 'm_transaction', count(*) FROM adempiere.m_transaction WHERE ad_client_id=1000000 UNION ALL SELECT 'r_request', count(*) FROM adempiere.r_request WHERE ad_client_id=1000000 UNION ALL SELECT 'c_bpartner', count(*) FROM adempiere.c_bpartner WHERE ad_client_id=1000000 UNION ALL SELECT 'm_product', count(*) FROM adempiere.m_product WHERE ad_client_id=1000000 ) x; COMMIT;