hanni76

Untitled

Oct 18th, 2025
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.76 KB | None | 0 0
  1. -- ============================================
  2. -- STEP 1: Install pg_background extension
  3. -- ============================================
  4. CREATE EXTENSION IF NOT EXISTS pg_background;
  5.  
  6. -- ============================================
  7. -- STEP 2: Create the results table
  8. -- ============================================
  9. DROP TABLE IF EXISTS task_results;
  10.  
  11. CREATE TABLE task_results (
  12. id SERIAL PRIMARY KEY,
  13. task_name TEXT NOT NULL,
  14. task_data JSONB,
  15. status TEXT DEFAULT 'pending',
  16. result JSONB,
  17. error_message TEXT,
  18. created_at TIMESTAMP DEFAULT NOW(),
  19. started_at TIMESTAMP,
  20. completed_at TIMESTAMP
  21. );
  22.  
  23. -- ============================================
  24. -- STEP 3: Create the long-running function
  25. -- ============================================
  26. CREATE OR REPLACE FUNCTION long_running_process(p_task_id INTEGER)
  27. RETURNS VOID AS $$
  28. DECLARE
  29. v_data JSONB;
  30. v_sleep_seconds INTEGER;
  31. BEGIN
  32. -- Update status to running
  33. UPDATE task_results
  34. SET status = 'running', started_at = NOW()
  35. WHERE id = p_task_id;
  36.  
  37. -- Get task data
  38. SELECT task_data INTO v_data
  39. FROM task_results
  40. WHERE id = p_task_id;
  41.  
  42. -- Get sleep time from data (default 10 seconds)
  43. v_sleep_seconds := COALESCE((v_data->>'sleep_seconds')::INTEGER, 10);
  44.  
  45. -- Simulate long work
  46. RAISE NOTICE 'Task % starting, will sleep for % seconds', p_task_id, v_sleep_seconds;
  47. PERFORM pg_sleep(v_sleep_seconds);
  48. RAISE NOTICE 'Task % completed after % seconds', p_task_id, v_sleep_seconds;
  49.  
  50. -- Mark as completed
  51. UPDATE task_results
  52. SET status = 'completed',
  53. result = jsonb_build_object(
  54. 'success', true,
  55. 'slept_for', v_sleep_seconds,
  56. 'message', 'Task completed successfully'
  57. ),
  58. completed_at = NOW()
  59. WHERE id = p_task_id;
  60.  
  61. EXCEPTION WHEN OTHERS THEN
  62. -- Handle errors
  63. UPDATE task_results
  64. SET status = 'failed',
  65. error_message = SQLERRM,
  66. completed_at = NOW()
  67. WHERE id = p_task_id;
  68.  
  69. RAISE NOTICE 'Task % failed: %', p_task_id, SQLERRM;
  70. END;
  71. $$ LANGUAGE plpgsql;
  72.  
  73. -- ============================================
  74. -- STEP 4: Create the async launcher
  75. -- ============================================
  76. CREATE OR REPLACE FUNCTION start_async_task(
  77. p_task_name TEXT,
  78. p_data JSONB DEFAULT '{}'::JSONB
  79. )
  80. RETURNS INTEGER AS $$
  81. DECLARE
  82. v_task_id INTEGER;
  83. BEGIN
  84. -- Insert task record
  85. INSERT INTO task_results (task_name, task_data, status)
  86. VALUES (p_task_name, p_data, 'pending')
  87. RETURNING id INTO v_task_id;
  88.  
  89. RAISE NOTICE 'Created task % with name: %', v_task_id, p_task_name;
  90.  
  91. -- Launch in background (this returns immediately!)
  92. PERFORM pg_background_launch(
  93. format('SELECT long_running_process(%s)', v_task_id)
  94. );
  95.  
  96. RAISE NOTICE 'Task % launched in background', v_task_id;
  97.  
  98. RETURN v_task_id;
  99. END;
  100. $$ LANGUAGE plpgsql;
  101.  
  102. -- ============================================
  103. -- STEP 5: Create helper polling function
  104. -- ============================================
  105. CREATE OR REPLACE FUNCTION get_task_info(p_task_id INTEGER)
  106. RETURNS TABLE (
  107. id INTEGER,
  108. task_name TEXT,
  109. status TEXT,
  110. result JSONB,
  111. error_message TEXT,
  112. created_at TIMESTAMP,
  113. started_at TIMESTAMP,
  114. completed_at TIMESTAMP,
  115. duration_seconds NUMERIC
  116. ) AS $$
  117. BEGIN
  118. RETURN QUERY
  119. SELECT
  120. t.id,
  121. t.task_name,
  122. t.status,
  123. t.result,
  124. t.error_message,
  125. t.created_at,
  126. t.started_at,
  127. t.completed_at,
  128. EXTRACT(EPOCH FROM (COALESCE(t.completed_at, NOW()) - t.created_at))::NUMERIC
  129. FROM task_results t
  130. WHERE t.id = p_task_id;
  131. END;
  132. $$ LANGUAGE plpgsql;
  133.  
  134. -- ============================================
  135. -- TESTING SECTION
  136. -- ============================================
  137.  
  138. -- TEST 1: Start a simple task (10 seconds)
  139. SELECT start_async_task('quick_test', '{"sleep_seconds": 10}'::JSONB);
  140. -- Output: Returns task_id (e.g., 1)
  141. -- This returns IMMEDIATELY
  142.  
  143. -- TEST 2: Check status immediately (should be 'pending' or 'running')
  144. SELECT id, task_name, status, created_at FROM task_results WHERE id = 1;
  145.  
  146. -- TEST 3: Wait a few seconds and check again
  147. SELECT pg_sleep(3);
  148. SELECT id, status, started_at FROM task_results WHERE id = 1;
  149. -- Status should now be 'running'
  150.  
  151. -- TEST 4: Wait for completion and check
  152. SELECT pg_sleep(8);
  153. SELECT * FROM get_task_info(1);
  154. -- Status should now be 'completed'
  155.  
  156. -- TEST 5: Start multiple tasks at once
  157. SELECT start_async_task('task_1', '{"sleep_seconds": 5}'::JSONB);
  158. SELECT start_async_task('task_2', '{"sleep_seconds": 8}'::JSONB);
  159. SELECT start_async_task('task_3', '{"sleep_seconds": 3}'::JSONB);
  160. -- All three return immediately with different task_ids
  161.  
  162. -- TEST 6: Monitor all tasks
  163. SELECT
  164. id,
  165. task_name,
  166. status,
  167. EXTRACT(EPOCH FROM (NOW() - created_at))::INTEGER as elapsed_seconds,
  168. CASE
  169. WHEN completed_at IS NOT NULL THEN
  170. EXTRACT(EPOCH FROM (completed_at - created_at))::INTEGER
  171. ELSE NULL
  172. END as total_duration
  173. FROM task_results
  174. ORDER BY id;
  175.  
  176. -- TEST 7: Watch task progress in real-time (run this in a loop)
  177. DO $$
  178. DECLARE
  179. v_status TEXT;
  180. v_count INTEGER := 0;
  181. BEGIN
  182. LOOP
  183. SELECT status INTO v_status FROM task_results WHERE id = 1;
  184. v_count := v_count + 1;
  185.  
  186. RAISE NOTICE 'Check %: Status = %', v_count, v_status;
  187.  
  188. EXIT WHEN v_status IN ('completed', 'failed') OR v_count > 20;
  189.  
  190. PERFORM pg_sleep(1);
  191. END LOOP;
  192. END $$;
  193.  
  194. -- TEST 8: Start a long task and disconnect
  195. -- Run this in one session:
  196. SELECT start_async_task('long_task', '{"sleep_seconds": 30}'::JSONB);
  197. -- Now close this connection/session
  198.  
  199. -- Run this in a NEW session to poll:
  200. SELECT * FROM get_task_info(currval('task_results_id_seq'));
  201.  
  202. -- TEST 9: Test error handling
  203. CREATE OR REPLACE FUNCTION failing_task(p_task_id INTEGER)
  204. RETURNS VOID AS $$
  205. BEGIN
  206. UPDATE task_results SET status = 'running' WHERE id = p_task_id;
  207. PERFORM pg_sleep(2);
  208. RAISE EXCEPTION 'Intentional error for testing';
  209. END;
  210. $$ LANGUAGE plpgsql;
  211.  
  212. -- Start a task that will fail
  213. SELECT start_async_task('error_test', '{}'::JSONB);
  214. -- Wait and check
  215. SELECT pg_sleep(3);
  216. SELECT id, status, error_message FROM task_results ORDER BY id DESC LIMIT 1;
  217.  
  218. -- TEST 10: View all tasks summary
  219. SELECT
  220. status,
  221. COUNT(*) as count,
  222. AVG(EXTRACT(EPOCH FROM (completed_at - created_at))) as avg_duration_seconds
  223. FROM task_results
  224. WHERE completed_at IS NOT NULL
  225. GROUP BY status;
  226.  
  227. -- TEST 11: Clean up test data
  228. -- TRUNCATE task_results;
  229. -- Or delete specific tests:
  230. -- DELETE FROM task_results WHERE id IN (1,2,3);
Advertisement
Add Comment
Please, Sign In to add comment