| 123456789101112131415161718192021222324252627282930313233343536373839 |
- -- Cache is accepted only after checking every dependency in the current DB snapshot.
- -- Financial commands never use this preview cache.
- CREATE OR REPLACE FUNCTION xs_payroll_preview_stamp(co text) RETURNS text LANGUAGE plpgsql SET jit=off SET work_mem='64MB' AS $$
- DECLARE tab text; parts text:='v2'; v text; meta text;
- BEGIN
- IF NOT xs_payroll_digest_ready() OR NOT xs_payroll_authority_ready() THEN RETURN NULL;END IF;
- SELECT md5(string_agg(kind||':'||signature,'|' ORDER BY kind)) INTO v FROM "PayrollReadDigestMeta";parts:=parts||'|'||v;
- FOREACH tab IN ARRAY ARRAY['LessonRecordFact','ReviewReward','ReviewRewardCorrection','TeachingCompensationRule','CourseTeachingProfile','CoachPayRate','TeachingCourseCategory','PayrollLine','PayrollBatch','LegacyDirectRun','LegacyDirectEvidence','LegacySyncConflict','PayrollCutoffDecision','PayrollCutoffApproval','_User','Node','CourseBinding'] LOOP
- -- xmin + physical tuple address detect updates even when updatedAt was not touched.
- -- Deletion, insertion, same-transaction rewrites, and rollback retain MVCC semantics.
- EXECUTE format('SELECT md5(COALESCE(string_agg(xmin::text||'':''||ctid::text,'','' ORDER BY ctid),'''')) FROM %I WHERE company=$1',tab) INTO v USING co;
- parts:=parts||'|'||tab||':'||v;
- END LOOP;
- SELECT md5(COALESCE(string_agg(c.oid||':'||c.relfilenode||':'||a.attnum||':'||a.attname||':'||a.atttypid||':'||a.atttypmod,'|' ORDER BY c.oid,a.attnum),'')) INTO meta
- FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace JOIN pg_attribute a ON a.attrelid=c.oid
- WHERE n.nspname='public' AND a.attnum>0 AND NOT a.attisdropped AND c.relname IN ('CommonModel','CourseAppointment','LessonRecord','MemoryPracticeRecord','LessonRecordFact','ReviewReward','ReviewRewardCorrection','TeachingCompensationRule','CourseTeachingProfile','CoachPayRate','TeachingCourseCategory','PayrollLine','PayrollBatch','LegacyDirectRun','LegacyDirectEvidence','LegacySyncConflict','PayrollCutoffDecision','PayrollCutoffApproval','_User','Node','CourseBinding','PayrollReadChange');
- parts:=parts||'|'||meta||'|'||COALESCE(pg_get_viewdef(to_regclass('"ReviewRewardIncome"'),true),'');
- SELECT md5(COALESCE(string_agg(pg_get_functiondef(p.oid),'|' ORDER BY p.oid),'')) INTO meta FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace WHERE n.nspname='public' AND p.proname LIKE 'xs_%' AND p.prokind='f';
- parts:=parts||'|'||meta||'|'||COALESCE((SELECT md5(code) FROM "Function" WHERE path='xiaoshu/ops/gateway-v3'),'')||'|'||current_setting('TimeZone')||'|'||current_setting('DateStyle')||'|'||current_setting('extra_float_digits');
- RETURN md5(parts);
- END $$;
- -- Top-level transaction IDs are checked against the build's original MVCC snapshot.
- -- This handles commits arriving out of order; max(id) and timestamps are insufficient.
- CREATE OR REPLACE FUNCTION xs_payroll_snapshot_current(co text, saved text) RETURNS boolean LANGUAGE sql STABLE AS $$
- SELECT saved IS NOT NULL AND NOT EXISTS(SELECT 1 FROM "PayrollReadChange" c WHERE c.company=co AND c.xid>=pg_snapshot_xmin(saved::pg_snapshot) AND NOT pg_visible_in_snapshot(c.xid,saved::pg_snapshot));
- $$;
- CREATE OR REPLACE FUNCTION xs_payroll_preview_cache_read(co text, cache_key text) RETURNS jsonb LANGUAGE plpgsql SET jit=off AS $$
- DECLARE signature text; result jsonb; read_snapshot text:=pg_current_snapshot()::text;
- BEGIN
- signature:=xs_payroll_preview_stamp(co);
- SELECT payload->'result' INTO result FROM "PayrollVerifiedReadCache" WHERE company=co AND key=cache_key AND stamp=signature AND "createdAt">now()-interval '1 day' AND xs_payroll_snapshot_current(co,payload->>'snapshot');
- RETURN jsonb_build_object('stamp',signature,'snapshot',read_snapshot,'payload',result);
- END $$;
- CREATE OR REPLACE FUNCTION xs_payroll_preview_cache_write(co text,cache_key text,expected text,result jsonb) RETURNS boolean LANGUAGE plpgsql SET jit=off AS $$
- BEGIN
- IF expected IS NULL OR expected IS DISTINCT FROM xs_payroll_preview_stamp(co) OR NOT xs_payroll_snapshot_current(co,result->>'snapshot') THEN RETURN false;END IF;
- INSERT INTO "PayrollVerifiedReadCache" VALUES(co,cache_key,expected,result,now()) ON CONFLICT(company,key) DO UPDATE SET stamp=EXCLUDED.stamp,payload=EXCLUDED.payload,"createdAt"=EXCLUDED."createdAt";
- RETURN true;
- END $$;
|