pilotswarm-horizon-store 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/dist/src/agent-tools.d.ts +32 -0
- package/dist/src/agent-tools.d.ts.map +1 -0
- package/dist/src/agent-tools.js +263 -0
- package/dist/src/agent-tools.js.map +1 -0
- package/dist/src/config.d.ts +93 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +120 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/db-retry.d.ts +21 -0
- package/dist/src/db-retry.d.ts.map +1 -0
- package/dist/src/db-retry.js +94 -0
- package/dist/src/db-retry.js.map +1 -0
- package/dist/src/embedding-client.d.ts +13 -0
- package/dist/src/embedding-client.d.ts.map +1 -0
- package/dist/src/embedding-client.js +79 -0
- package/dist/src/embedding-client.js.map +1 -0
- package/dist/src/graph-model.d.ts +49 -0
- package/dist/src/graph-model.d.ts.map +1 -0
- package/dist/src/graph-model.js +112 -0
- package/dist/src/graph-model.js.map +1 -0
- package/dist/src/graph-queries.d.ts +83 -0
- package/dist/src/graph-queries.d.ts.map +1 -0
- package/dist/src/graph-queries.js +727 -0
- package/dist/src/graph-queries.js.map +1 -0
- package/dist/src/graph-store.d.ts +57 -0
- package/dist/src/graph-store.d.ts.map +1 -0
- package/dist/src/graph-store.js +327 -0
- package/dist/src/graph-store.js.map +1 -0
- package/dist/src/horizon-migrator.d.ts +49 -0
- package/dist/src/horizon-migrator.d.ts.map +1 -0
- package/dist/src/horizon-migrator.js +231 -0
- package/dist/src/horizon-migrator.js.map +1 -0
- package/dist/src/horizon-store.d.ts +116 -0
- package/dist/src/horizon-store.d.ts.map +1 -0
- package/dist/src/horizon-store.js +563 -0
- package/dist/src/horizon-store.js.map +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/preconditions.d.ts +16 -0
- package/dist/src/preconditions.d.ts.map +1 -0
- package/dist/src/preconditions.js +99 -0
- package/dist/src/preconditions.js.map +1 -0
- package/dist/src/query-builder.d.ts +37 -0
- package/dist/src/query-builder.d.ts.map +1 -0
- package/dist/src/query-builder.js +82 -0
- package/dist/src/query-builder.js.map +1 -0
- package/dist/src/sql-util.d.ts +20 -0
- package/dist/src/sql-util.d.ts.map +1 -0
- package/dist/src/sql-util.js +40 -0
- package/dist/src/sql-util.js.map +1 -0
- package/dist/src/types.d.ts +16 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +35 -0
- package/dist/src/types.js.map +1 -0
- package/migrations/0001_facts_table.sql +77 -0
- package/migrations/0002_indexes.sql +27 -0
- package/migrations/0003_age_bootstrap.sql +13 -0
- package/migrations/0004_facts_procs.sql +253 -0
- package/migrations/0005_embedder_workflow.sql +119 -0
- package/migrations/0006_facts_read_uncrawled_embedded_gate.sql +33 -0
- package/migrations/0007_embed_key_value_text.sql +122 -0
- package/migrations/0008_embedder_failure_recovery.sql +409 -0
- package/migrations/0009_search_text_full_value.sql +52 -0
- package/migrations/0010_minimal_two_loop_embedder.sql +392 -0
- package/migrations/0011_unified_api_embedder_workflow.sql +273 -0
- package/migrations/0012_facts_soft_delete.sql +587 -0
- package/migrations/0013_graph_namespaces.sql +60 -0
- package/package.json +69 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
-- 0010_minimal_two_loop_embedder — finalize the minimal embedder state model
|
|
2
|
+
-- and split pg_durable embedding into independent batch and retry loops.
|
|
3
|
+
--
|
|
4
|
+
-- Final embedding state:
|
|
5
|
+
-- embedding, embedding_model, last_embed_error
|
|
6
|
+
--
|
|
7
|
+
-- last_embed_error convention:
|
|
8
|
+
-- NULL = healthy/eligible
|
|
9
|
+
-- -1 = internal single-row retry marker
|
|
10
|
+
-- > 0 = terminal row-level embedding failure
|
|
11
|
+
--
|
|
12
|
+
-- The embedder never reads or writes last_crawled_at. Graph crawler paths own
|
|
13
|
+
-- that column.
|
|
14
|
+
--
|
|
15
|
+
-- Tokens: {{SCHEMA}}.
|
|
16
|
+
|
|
17
|
+
ALTER TABLE "{{SCHEMA}}".facts ADD COLUMN IF NOT EXISTS last_embed_error INT;
|
|
18
|
+
ALTER TABLE "{{SCHEMA}}".facts DROP COLUMN IF EXISTS content_hash;
|
|
19
|
+
ALTER TABLE "{{SCHEMA}}".facts DROP COLUMN IF EXISTS last_embedded_hash;
|
|
20
|
+
ALTER TABLE "{{SCHEMA}}".facts DROP COLUMN IF EXISTS embedded_at;
|
|
21
|
+
ALTER TABLE "{{SCHEMA}}".facts DROP COLUMN IF EXISTS last_embed_error_at;
|
|
22
|
+
ALTER TABLE "{{SCHEMA}}".facts DROP COLUMN IF EXISTS embed_retry_at;
|
|
23
|
+
|
|
24
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".embedder_workflow(int, int);
|
|
25
|
+
|
|
26
|
+
DROP INDEX IF EXISTS "{{SCHEMA}}".idx_facts_embed_retry;
|
|
27
|
+
DROP INDEX IF EXISTS "{{SCHEMA}}".idx_facts_last_embed_error;
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_facts_embed_retry
|
|
29
|
+
ON "{{SCHEMA}}".facts (id) WHERE last_embed_error = -1;
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_facts_last_embed_error
|
|
31
|
+
ON "{{SCHEMA}}".facts (last_embed_error, id) WHERE last_embed_error > 0;
|
|
32
|
+
|
|
33
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".embed_error_code(p_status INT, p_body TEXT) RETURNS INT
|
|
34
|
+
LANGUAGE sql IMMUTABLE AS $$
|
|
35
|
+
SELECT CASE
|
|
36
|
+
WHEN p_body ILIKE '%maximum input length%'
|
|
37
|
+
OR p_body ILIKE '%maximum context length%'
|
|
38
|
+
OR p_body ILIKE '%too many tokens%'
|
|
39
|
+
OR p_body ILIKE '%too%many%token%'
|
|
40
|
+
OR p_body ILIKE '%input%too%long%'
|
|
41
|
+
OR p_body ILIKE '%token%limit%'
|
|
42
|
+
OR p_body ILIKE '%exceed%token%'
|
|
43
|
+
OR p_body ILIKE '%requested%token%'
|
|
44
|
+
THEN 1001
|
|
45
|
+
WHEN p_status = 400 THEN 1400
|
|
46
|
+
WHEN p_status = 401 THEN 1401
|
|
47
|
+
WHEN p_status = 403 THEN 1403
|
|
48
|
+
WHEN p_status = 429 THEN 1429
|
|
49
|
+
WHEN p_status BETWEEN 500 AND 599 THEN 1500
|
|
50
|
+
ELSE 9999
|
|
51
|
+
END;
|
|
52
|
+
$$;
|
|
53
|
+
|
|
54
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".facts_touch() RETURNS trigger
|
|
55
|
+
LANGUAGE plpgsql AS $$
|
|
56
|
+
BEGIN
|
|
57
|
+
IF TG_OP = 'INSERT' OR NEW.key IS DISTINCT FROM OLD.key OR NEW.value IS DISTINCT FROM OLD.value THEN
|
|
58
|
+
NEW.updated_at := now();
|
|
59
|
+
NEW.last_crawled_at := NULL;
|
|
60
|
+
NEW.embedding := NULL;
|
|
61
|
+
NEW.embedding_model := NULL;
|
|
62
|
+
NEW.last_embed_error := NULL;
|
|
63
|
+
END IF;
|
|
64
|
+
RETURN NEW;
|
|
65
|
+
END $$;
|
|
66
|
+
|
|
67
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".facts_store_batch(p_facts JSONB)
|
|
68
|
+
RETURNS INT
|
|
69
|
+
LANGUAGE sql AS $$
|
|
70
|
+
WITH input AS (
|
|
71
|
+
SELECT
|
|
72
|
+
e->>'scopeKey' AS scope_key,
|
|
73
|
+
e->>'key' AS key,
|
|
74
|
+
e->'value' AS value,
|
|
75
|
+
e->>'agentId' AS agent_id,
|
|
76
|
+
e->>'sessionId' AS session_id,
|
|
77
|
+
coalesce((e->>'shared')::boolean, false) AS shared,
|
|
78
|
+
coalesce(ARRAY(SELECT jsonb_array_elements_text(e->'tags')), '{}'::text[]) AS tags
|
|
79
|
+
FROM jsonb_array_elements(p_facts) e
|
|
80
|
+
), upserted AS (
|
|
81
|
+
INSERT INTO "{{SCHEMA}}".facts (scope_key, key, value, agent_id, session_id, shared, transient, tags, updated_at)
|
|
82
|
+
SELECT scope_key, key, value, agent_id, session_id, shared, NOT shared, tags, now()
|
|
83
|
+
FROM input
|
|
84
|
+
ON CONFLICT (scope_key) DO UPDATE SET
|
|
85
|
+
value = EXCLUDED.value,
|
|
86
|
+
agent_id = EXCLUDED.agent_id,
|
|
87
|
+
session_id = EXCLUDED.session_id,
|
|
88
|
+
shared = EXCLUDED.shared,
|
|
89
|
+
transient = EXCLUDED.transient,
|
|
90
|
+
tags = EXCLUDED.tags,
|
|
91
|
+
updated_at = now()
|
|
92
|
+
RETURNING 1
|
|
93
|
+
)
|
|
94
|
+
SELECT count(*)::int FROM upserted;
|
|
95
|
+
$$;
|
|
96
|
+
|
|
97
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".facts_store(
|
|
98
|
+
p_scope_key TEXT,
|
|
99
|
+
p_key TEXT,
|
|
100
|
+
p_value JSONB,
|
|
101
|
+
p_agent_id TEXT,
|
|
102
|
+
p_session_id TEXT,
|
|
103
|
+
p_shared BOOLEAN,
|
|
104
|
+
p_tags TEXT[]
|
|
105
|
+
) RETURNS void
|
|
106
|
+
LANGUAGE sql AS $$
|
|
107
|
+
SELECT "{{SCHEMA}}".facts_store_batch(jsonb_build_array(jsonb_build_object(
|
|
108
|
+
'scopeKey', p_scope_key,
|
|
109
|
+
'key', p_key,
|
|
110
|
+
'value', p_value,
|
|
111
|
+
'agentId', p_agent_id,
|
|
112
|
+
'sessionId', p_session_id,
|
|
113
|
+
'shared', p_shared,
|
|
114
|
+
'tags', coalesce(p_tags, '{}'::text[])
|
|
115
|
+
)));
|
|
116
|
+
$$;
|
|
117
|
+
|
|
118
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".facts_delete_pattern(
|
|
119
|
+
p_key_pattern TEXT,
|
|
120
|
+
p_scope TEXT,
|
|
121
|
+
p_session_id TEXT,
|
|
122
|
+
p_unrestricted BOOLEAN DEFAULT FALSE
|
|
123
|
+
) RETURNS INT
|
|
124
|
+
LANGUAGE plpgsql AS $$
|
|
125
|
+
DECLARE
|
|
126
|
+
deleted_count INT;
|
|
127
|
+
BEGIN
|
|
128
|
+
IF p_key_pattern IS NULL OR p_key_pattern = '' THEN
|
|
129
|
+
RAISE EXCEPTION 'facts_delete_pattern requires a key pattern';
|
|
130
|
+
END IF;
|
|
131
|
+
IF p_scope NOT IN ('session', 'shared', 'all') THEN
|
|
132
|
+
RAISE EXCEPTION 'facts_delete_pattern scope must be session, shared, or all';
|
|
133
|
+
END IF;
|
|
134
|
+
IF p_scope = 'all' AND p_unrestricted IS DISTINCT FROM TRUE THEN
|
|
135
|
+
RAISE EXCEPTION 'facts_delete_pattern scope=all requires unrestricted=true';
|
|
136
|
+
END IF;
|
|
137
|
+
IF p_scope = 'session' AND p_session_id IS NULL THEN
|
|
138
|
+
RAISE EXCEPTION 'facts_delete_pattern scope=session requires sessionId';
|
|
139
|
+
END IF;
|
|
140
|
+
|
|
141
|
+
DELETE FROM "{{SCHEMA}}".facts f
|
|
142
|
+
WHERE f.key LIKE p_key_pattern
|
|
143
|
+
AND (
|
|
144
|
+
(p_scope = 'shared' AND f.shared = TRUE)
|
|
145
|
+
OR (p_scope = 'session' AND f.shared = FALSE AND f.session_id = p_session_id)
|
|
146
|
+
OR (p_scope = 'all' AND p_unrestricted = TRUE)
|
|
147
|
+
);
|
|
148
|
+
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
|
149
|
+
RETURN deleted_count;
|
|
150
|
+
END;
|
|
151
|
+
$$;
|
|
152
|
+
|
|
153
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".facts_embedding_failures(TEXT, INT[], INT);
|
|
154
|
+
|
|
155
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".embedder_batch_workflow(p_interval int, p_batch int)
|
|
156
|
+
RETURNS text
|
|
157
|
+
LANGUAGE plpgsql AS $fn$
|
|
158
|
+
DECLARE
|
|
159
|
+
v_url text;
|
|
160
|
+
v_model text;
|
|
161
|
+
v_key text;
|
|
162
|
+
v_keyhdr text;
|
|
163
|
+
v_bearer text;
|
|
164
|
+
v_inputfield text;
|
|
165
|
+
v_timeout int;
|
|
166
|
+
v_headers jsonb;
|
|
167
|
+
v_batch_sql text;
|
|
168
|
+
v_batch_too_large_sql text;
|
|
169
|
+
v_resp_ok_sql text;
|
|
170
|
+
v_batch_fail_sql text;
|
|
171
|
+
v_zip_sql text;
|
|
172
|
+
BEGIN
|
|
173
|
+
v_url := df.getvar('hz_{{SCHEMA}}_url');
|
|
174
|
+
v_model := df.getvar('hz_{{SCHEMA}}_model');
|
|
175
|
+
v_key := df.getvar('hz_{{SCHEMA}}_key');
|
|
176
|
+
v_keyhdr := coalesce(df.getvar('hz_{{SCHEMA}}_keyhdr'), 'api-key');
|
|
177
|
+
v_bearer := coalesce(df.getvar('hz_{{SCHEMA}}_bearer'), 'false');
|
|
178
|
+
v_inputfield := coalesce(df.getvar('hz_{{SCHEMA}}_inputfield'), 'input');
|
|
179
|
+
v_timeout := coalesce(df.getvar('hz_{{SCHEMA}}_timeout'), '30')::int;
|
|
180
|
+
|
|
181
|
+
IF v_url IS NULL OR v_model IS NULL THEN
|
|
182
|
+
RAISE EXCEPTION 'embedder not configured: call configureEmbedder first';
|
|
183
|
+
END IF;
|
|
184
|
+
|
|
185
|
+
v_headers := jsonb_build_object('content-type', 'application/json');
|
|
186
|
+
IF v_key IS NOT NULL AND v_key <> '' THEN
|
|
187
|
+
v_headers := v_headers || jsonb_build_object(
|
|
188
|
+
v_keyhdr,
|
|
189
|
+
CASE WHEN v_bearer = 'true' THEN 'Bearer ' || v_key ELSE v_key END
|
|
190
|
+
);
|
|
191
|
+
END IF;
|
|
192
|
+
|
|
193
|
+
v_batch_sql := format(
|
|
194
|
+
$q$SELECT jsonb_build_object(%L, jsonb_agg(t.txt ORDER BY t.id), 'model', %L)::text AS body,
|
|
195
|
+
array_agg(t.id ORDER BY t.id)::text AS ids,
|
|
196
|
+
array_agg(t.updated_at ORDER BY t.id)::text AS updated_ats,
|
|
197
|
+
bool_or(t.input_chars > 8000)::text AS has_oversized
|
|
198
|
+
FROM (SELECT id, updated_at,
|
|
199
|
+
coalesce(key, '') || E'\n' || coalesce(value::text, '') AS txt,
|
|
200
|
+
char_length(coalesce(key, '') || E'\n' || coalesce(value::text, '')) AS input_chars
|
|
201
|
+
FROM "{{SCHEMA}}".facts
|
|
202
|
+
WHERE last_embed_error IS NULL
|
|
203
|
+
AND (embedding IS NULL OR embedding_model IS DISTINCT FROM %L)
|
|
204
|
+
ORDER BY id
|
|
205
|
+
LIMIT %s) t
|
|
206
|
+
HAVING count(*) > 0$q$,
|
|
207
|
+
v_inputfield, v_model, v_model, greatest(p_batch, 1));
|
|
208
|
+
|
|
209
|
+
v_batch_too_large_sql := $q$SELECT 1 WHERE coalesce(($batch.has_oversized)::boolean, false)$q$;
|
|
210
|
+
|
|
211
|
+
v_resp_ok_sql := $q$SELECT 1
|
|
212
|
+
WHERE "{{SCHEMA}}".embed_response_data_count($resp::jsonb) = cardinality(($batch.ids)::bigint[])$q$;
|
|
213
|
+
|
|
214
|
+
v_batch_fail_sql := $q$WITH selected AS (
|
|
215
|
+
SELECT u.id, u.seen_updated_at
|
|
216
|
+
FROM unnest(($batch.ids)::bigint[], ($batch.updated_ats)::timestamptz[]) AS u(id, seen_updated_at)
|
|
217
|
+
)
|
|
218
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
219
|
+
SET last_embed_error = -1,
|
|
220
|
+
embedding = NULL,
|
|
221
|
+
embedding_model = NULL
|
|
222
|
+
FROM selected s
|
|
223
|
+
WHERE f.id = s.id
|
|
224
|
+
AND f.updated_at IS NOT DISTINCT FROM s.seen_updated_at
|
|
225
|
+
AND f.last_embed_error IS NULL$q$;
|
|
226
|
+
|
|
227
|
+
v_zip_sql := format(
|
|
228
|
+
$q$WITH resp AS (SELECT ($resp::jsonb->>'body')::jsonb AS j),
|
|
229
|
+
emb AS (SELECT v.ord, v.e->'embedding' AS vec
|
|
230
|
+
FROM resp, jsonb_array_elements(resp.j->'data') WITH ORDINALITY AS v(e, ord)),
|
|
231
|
+
tgt AS (SELECT u.id, u.seen_updated_at, u.ord
|
|
232
|
+
FROM unnest(($batch.ids)::bigint[], ($batch.updated_ats)::timestamptz[]) WITH ORDINALITY AS u(id, seen_updated_at, ord)),
|
|
233
|
+
upd AS (
|
|
234
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
235
|
+
SET embedding = (emb.vec)::text::vector,
|
|
236
|
+
embedding_model = %L,
|
|
237
|
+
last_embed_error = NULL
|
|
238
|
+
FROM emb JOIN tgt ON tgt.ord = emb.ord
|
|
239
|
+
WHERE f.id = tgt.id
|
|
240
|
+
AND f.updated_at IS NOT DISTINCT FROM tgt.seen_updated_at
|
|
241
|
+
RETURNING f.id)
|
|
242
|
+
SELECT count(*) FROM upd$q$,
|
|
243
|
+
v_model);
|
|
244
|
+
|
|
245
|
+
RETURN df.loop(
|
|
246
|
+
df.seq(
|
|
247
|
+
df.sleep(greatest(p_interval, 1)::bigint),
|
|
248
|
+
df.seq(
|
|
249
|
+
df.as(df.sql(v_batch_sql), 'batch'),
|
|
250
|
+
df.if_rows('batch',
|
|
251
|
+
df.seq(
|
|
252
|
+
df.as(df.sql(v_batch_too_large_sql), 'batch_too_large'),
|
|
253
|
+
df.if_rows('batch_too_large',
|
|
254
|
+
df.sql(v_batch_fail_sql),
|
|
255
|
+
df.seq(
|
|
256
|
+
df.as(df.http(v_url, 'POST', '$batch.body', v_headers, v_timeout), 'resp'),
|
|
257
|
+
df.seq(
|
|
258
|
+
df.as(df.sql(v_resp_ok_sql), 'resp_ok'),
|
|
259
|
+
df.if_rows('resp_ok', df.sql(v_zip_sql), df.sql(v_batch_fail_sql)))))),
|
|
260
|
+
df.sql('SELECT 1')))),
|
|
261
|
+
NULL);
|
|
262
|
+
END $fn$;
|
|
263
|
+
|
|
264
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".embedder_retry_workflow(p_interval int)
|
|
265
|
+
RETURNS text
|
|
266
|
+
LANGUAGE plpgsql AS $fn$
|
|
267
|
+
DECLARE
|
|
268
|
+
v_url text;
|
|
269
|
+
v_model text;
|
|
270
|
+
v_key text;
|
|
271
|
+
v_keyhdr text;
|
|
272
|
+
v_bearer text;
|
|
273
|
+
v_inputfield text;
|
|
274
|
+
v_timeout int;
|
|
275
|
+
v_headers jsonb;
|
|
276
|
+
v_retry_sql text;
|
|
277
|
+
v_retry_too_large_sql text;
|
|
278
|
+
v_retry_resp_ok_sql text;
|
|
279
|
+
v_retry_zip_sql text;
|
|
280
|
+
v_retry_fail_sql text;
|
|
281
|
+
v_retry_oversized_fail_sql text;
|
|
282
|
+
BEGIN
|
|
283
|
+
v_url := df.getvar('hz_{{SCHEMA}}_url');
|
|
284
|
+
v_model := df.getvar('hz_{{SCHEMA}}_model');
|
|
285
|
+
v_key := df.getvar('hz_{{SCHEMA}}_key');
|
|
286
|
+
v_keyhdr := coalesce(df.getvar('hz_{{SCHEMA}}_keyhdr'), 'api-key');
|
|
287
|
+
v_bearer := coalesce(df.getvar('hz_{{SCHEMA}}_bearer'), 'false');
|
|
288
|
+
v_inputfield := coalesce(df.getvar('hz_{{SCHEMA}}_inputfield'), 'input');
|
|
289
|
+
v_timeout := coalesce(df.getvar('hz_{{SCHEMA}}_timeout'), '30')::int;
|
|
290
|
+
|
|
291
|
+
IF v_url IS NULL OR v_model IS NULL THEN
|
|
292
|
+
RAISE EXCEPTION 'embedder not configured: call configureEmbedder first';
|
|
293
|
+
END IF;
|
|
294
|
+
|
|
295
|
+
v_headers := jsonb_build_object('content-type', 'application/json');
|
|
296
|
+
IF v_key IS NOT NULL AND v_key <> '' THEN
|
|
297
|
+
v_headers := v_headers || jsonb_build_object(
|
|
298
|
+
v_keyhdr,
|
|
299
|
+
CASE WHEN v_bearer = 'true' THEN 'Bearer ' || v_key ELSE v_key END
|
|
300
|
+
);
|
|
301
|
+
END IF;
|
|
302
|
+
|
|
303
|
+
v_retry_sql := format(
|
|
304
|
+
$q$SELECT jsonb_build_object(%L, jsonb_agg(t.txt ORDER BY t.id), 'model', %L)::text AS body,
|
|
305
|
+
array_agg(t.id ORDER BY t.id)::text AS ids,
|
|
306
|
+
array_agg(t.updated_at ORDER BY t.id)::text AS updated_ats,
|
|
307
|
+
bool_or(t.input_chars > 8000)::text AS has_oversized
|
|
308
|
+
FROM (SELECT id, updated_at,
|
|
309
|
+
coalesce(key, '') || E'\n' || coalesce(value::text, '') AS txt,
|
|
310
|
+
char_length(coalesce(key, '') || E'\n' || coalesce(value::text, '')) AS input_chars
|
|
311
|
+
FROM "{{SCHEMA}}".facts
|
|
312
|
+
WHERE last_embed_error = -1
|
|
313
|
+
ORDER BY id
|
|
314
|
+
LIMIT 1) t
|
|
315
|
+
HAVING count(*) > 0$q$,
|
|
316
|
+
v_inputfield, v_model);
|
|
317
|
+
|
|
318
|
+
v_retry_too_large_sql := $q$SELECT 1 WHERE coalesce(($retry.has_oversized)::boolean, false)$q$;
|
|
319
|
+
|
|
320
|
+
v_retry_resp_ok_sql := $q$SELECT 1
|
|
321
|
+
WHERE "{{SCHEMA}}".embed_response_data_count($retry_resp::jsonb) = cardinality(($retry.ids)::bigint[])$q$;
|
|
322
|
+
|
|
323
|
+
v_retry_zip_sql := format(
|
|
324
|
+
$q$WITH resp AS (SELECT ($retry_resp::jsonb->>'body')::jsonb AS j),
|
|
325
|
+
emb AS (SELECT v.ord, v.e->'embedding' AS vec
|
|
326
|
+
FROM resp, jsonb_array_elements(resp.j->'data') WITH ORDINALITY AS v(e, ord)),
|
|
327
|
+
tgt AS (SELECT u.id, u.seen_updated_at, u.ord
|
|
328
|
+
FROM unnest(($retry.ids)::bigint[], ($retry.updated_ats)::timestamptz[]) WITH ORDINALITY AS u(id, seen_updated_at, ord)),
|
|
329
|
+
upd AS (
|
|
330
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
331
|
+
SET embedding = (emb.vec)::text::vector,
|
|
332
|
+
embedding_model = %L,
|
|
333
|
+
last_embed_error = NULL
|
|
334
|
+
FROM emb JOIN tgt ON tgt.ord = emb.ord
|
|
335
|
+
WHERE f.id = tgt.id
|
|
336
|
+
AND f.updated_at IS NOT DISTINCT FROM tgt.seen_updated_at
|
|
337
|
+
RETURNING f.id)
|
|
338
|
+
SELECT count(*) FROM upd$q$,
|
|
339
|
+
v_model);
|
|
340
|
+
|
|
341
|
+
v_retry_fail_sql := $q$WITH resp AS (
|
|
342
|
+
SELECT $retry_resp::jsonb AS r
|
|
343
|
+
), tgt AS (
|
|
344
|
+
SELECT u.id, u.seen_updated_at
|
|
345
|
+
FROM unnest(($retry.ids)::bigint[], ($retry.updated_ats)::timestamptz[]) AS u(id, seen_updated_at)
|
|
346
|
+
), classified AS (
|
|
347
|
+
SELECT tgt.id, tgt.seen_updated_at,
|
|
348
|
+
"{{SCHEMA}}".embed_response_terminal_code(resp.r) AS code
|
|
349
|
+
FROM tgt, resp
|
|
350
|
+
), upd AS (
|
|
351
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
352
|
+
SET last_embed_error = classified.code,
|
|
353
|
+
embedding = NULL,
|
|
354
|
+
embedding_model = NULL
|
|
355
|
+
FROM classified
|
|
356
|
+
WHERE f.id = classified.id
|
|
357
|
+
AND f.updated_at IS NOT DISTINCT FROM classified.seen_updated_at
|
|
358
|
+
RETURNING f.id)
|
|
359
|
+
SELECT count(*) FROM upd$q$;
|
|
360
|
+
|
|
361
|
+
v_retry_oversized_fail_sql := $q$WITH tgt AS (
|
|
362
|
+
SELECT u.id, u.seen_updated_at
|
|
363
|
+
FROM unnest(($retry.ids)::bigint[], ($retry.updated_ats)::timestamptz[]) AS u(id, seen_updated_at)
|
|
364
|
+
), upd AS (
|
|
365
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
366
|
+
SET last_embed_error = 1001,
|
|
367
|
+
embedding = NULL,
|
|
368
|
+
embedding_model = NULL
|
|
369
|
+
FROM tgt
|
|
370
|
+
WHERE f.id = tgt.id
|
|
371
|
+
AND f.updated_at IS NOT DISTINCT FROM tgt.seen_updated_at
|
|
372
|
+
RETURNING f.id)
|
|
373
|
+
SELECT count(*) FROM upd$q$;
|
|
374
|
+
|
|
375
|
+
RETURN df.loop(
|
|
376
|
+
df.seq(
|
|
377
|
+
df.sleep(greatest(p_interval, 1)::bigint),
|
|
378
|
+
df.seq(
|
|
379
|
+
df.as(df.sql(v_retry_sql), 'retry'),
|
|
380
|
+
df.if_rows('retry',
|
|
381
|
+
df.seq(
|
|
382
|
+
df.as(df.sql(v_retry_too_large_sql), 'retry_too_large'),
|
|
383
|
+
df.if_rows('retry_too_large',
|
|
384
|
+
df.sql(v_retry_oversized_fail_sql),
|
|
385
|
+
df.seq(
|
|
386
|
+
df.as(df.http(v_url, 'POST', '$retry.body', v_headers, v_timeout), 'retry_resp'),
|
|
387
|
+
df.seq(
|
|
388
|
+
df.as(df.sql(v_retry_resp_ok_sql), 'retry_resp_ok'),
|
|
389
|
+
df.if_rows('retry_resp_ok', df.sql(v_retry_zip_sql), df.sql(v_retry_fail_sql)))))),
|
|
390
|
+
df.sql('SELECT 1')))),
|
|
391
|
+
NULL);
|
|
392
|
+
END $fn$;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
-- 0011_unified_api_embedder_workflow — collapse public proc names and expose
|
|
2
|
+
-- one internal embedder workflow function started in two durable modes.
|
|
3
|
+
--
|
|
4
|
+
-- Public-ish provider procs after this migration:
|
|
5
|
+
-- facts_store(jsonb) -- single or batch input
|
|
6
|
+
-- facts_delete(text, boolean, text, text, boolean) -- exact or pattern
|
|
7
|
+
-- embedder_workflow(text, int, int) -- mode = batch | retry
|
|
8
|
+
--
|
|
9
|
+
-- The runtime still starts two durable loop instances so batch work and one-row
|
|
10
|
+
-- retries do not block each other. They are modes of one workflow, not two
|
|
11
|
+
-- separate schema concepts.
|
|
12
|
+
|
|
13
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".facts_store(JSONB);
|
|
14
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".facts_store(p_facts JSONB)
|
|
15
|
+
RETURNS INT
|
|
16
|
+
LANGUAGE sql AS $$
|
|
17
|
+
WITH input AS (
|
|
18
|
+
SELECT
|
|
19
|
+
e->>'scopeKey' AS scope_key,
|
|
20
|
+
e->>'key' AS key,
|
|
21
|
+
e->'value' AS value,
|
|
22
|
+
e->>'agentId' AS agent_id,
|
|
23
|
+
e->>'sessionId' AS session_id,
|
|
24
|
+
coalesce((e->>'shared')::boolean, false) AS shared,
|
|
25
|
+
coalesce(ARRAY(SELECT jsonb_array_elements_text(e->'tags')), '{}'::text[]) AS tags
|
|
26
|
+
FROM jsonb_array_elements(p_facts) e
|
|
27
|
+
), upserted AS (
|
|
28
|
+
INSERT INTO "{{SCHEMA}}".facts (scope_key, key, value, agent_id, session_id, shared, transient, tags, updated_at)
|
|
29
|
+
SELECT scope_key, key, value, agent_id, session_id, shared, NOT shared, tags, now()
|
|
30
|
+
FROM input
|
|
31
|
+
ON CONFLICT (scope_key) DO UPDATE SET
|
|
32
|
+
value = EXCLUDED.value,
|
|
33
|
+
agent_id = EXCLUDED.agent_id,
|
|
34
|
+
session_id = EXCLUDED.session_id,
|
|
35
|
+
shared = EXCLUDED.shared,
|
|
36
|
+
transient = EXCLUDED.transient,
|
|
37
|
+
tags = EXCLUDED.tags,
|
|
38
|
+
updated_at = now()
|
|
39
|
+
RETURNING 1
|
|
40
|
+
)
|
|
41
|
+
SELECT count(*)::int FROM upserted;
|
|
42
|
+
$$;
|
|
43
|
+
|
|
44
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".facts_store(TEXT, TEXT, JSONB, TEXT, TEXT, BOOLEAN, TEXT[]);
|
|
45
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".facts_store_batch(JSONB);
|
|
46
|
+
|
|
47
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".facts_delete(TEXT, BOOLEAN, TEXT, TEXT, BOOLEAN);
|
|
48
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".facts_delete(
|
|
49
|
+
p_key_or_pattern TEXT,
|
|
50
|
+
p_pattern BOOLEAN DEFAULT FALSE,
|
|
51
|
+
p_scope TEXT DEFAULT NULL,
|
|
52
|
+
p_session_id TEXT DEFAULT NULL,
|
|
53
|
+
p_unrestricted BOOLEAN DEFAULT FALSE
|
|
54
|
+
) RETURNS INT
|
|
55
|
+
LANGUAGE plpgsql AS $$
|
|
56
|
+
DECLARE
|
|
57
|
+
deleted_count INT;
|
|
58
|
+
v_scope TEXT;
|
|
59
|
+
BEGIN
|
|
60
|
+
IF p_pattern THEN
|
|
61
|
+
IF p_key_or_pattern IS NULL OR p_key_or_pattern = '' THEN
|
|
62
|
+
RAISE EXCEPTION 'facts_delete pattern mode requires key';
|
|
63
|
+
END IF;
|
|
64
|
+
v_scope := coalesce(p_scope, 'session');
|
|
65
|
+
IF v_scope NOT IN ('session', 'shared', 'all') THEN
|
|
66
|
+
RAISE EXCEPTION 'facts_delete scope must be session, shared, or all';
|
|
67
|
+
END IF;
|
|
68
|
+
IF v_scope = 'all' AND p_unrestricted IS DISTINCT FROM TRUE THEN
|
|
69
|
+
RAISE EXCEPTION 'facts_delete scope=all requires unrestricted=true';
|
|
70
|
+
END IF;
|
|
71
|
+
IF v_scope = 'session' AND p_session_id IS NULL THEN
|
|
72
|
+
RAISE EXCEPTION 'facts_delete scope=session requires sessionId';
|
|
73
|
+
END IF;
|
|
74
|
+
|
|
75
|
+
DELETE FROM "{{SCHEMA}}".facts f
|
|
76
|
+
WHERE f.key LIKE p_key_or_pattern
|
|
77
|
+
AND (
|
|
78
|
+
(v_scope = 'shared' AND f.shared = TRUE)
|
|
79
|
+
OR (v_scope = 'session' AND f.shared = FALSE AND f.session_id = p_session_id)
|
|
80
|
+
OR (v_scope = 'all' AND p_unrestricted = TRUE)
|
|
81
|
+
);
|
|
82
|
+
ELSE
|
|
83
|
+
DELETE FROM "{{SCHEMA}}".facts WHERE scope_key = p_key_or_pattern;
|
|
84
|
+
END IF;
|
|
85
|
+
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
|
86
|
+
RETURN deleted_count;
|
|
87
|
+
END;
|
|
88
|
+
$$;
|
|
89
|
+
|
|
90
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".facts_delete(TEXT);
|
|
91
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".facts_delete_pattern(TEXT, TEXT, TEXT, BOOLEAN);
|
|
92
|
+
|
|
93
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".embedder_workflow(TEXT, INT, INT);
|
|
94
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".embedder_workflow(p_mode text, p_interval int, p_batch int)
|
|
95
|
+
RETURNS text
|
|
96
|
+
LANGUAGE plpgsql AS $fn$
|
|
97
|
+
DECLARE
|
|
98
|
+
v_url text;
|
|
99
|
+
v_model text;
|
|
100
|
+
v_key text;
|
|
101
|
+
v_keyhdr text;
|
|
102
|
+
v_bearer text;
|
|
103
|
+
v_inputfield text;
|
|
104
|
+
v_timeout int;
|
|
105
|
+
v_headers jsonb;
|
|
106
|
+
v_select_sql text;
|
|
107
|
+
v_too_large_sql text;
|
|
108
|
+
v_resp_ok_sql text;
|
|
109
|
+
v_batch_fail_sql text;
|
|
110
|
+
v_zip_sql text;
|
|
111
|
+
v_retry_fail_sql text;
|
|
112
|
+
v_oversized_fail_sql text;
|
|
113
|
+
v_alias text;
|
|
114
|
+
v_resp_alias text;
|
|
115
|
+
v_limit int;
|
|
116
|
+
BEGIN
|
|
117
|
+
IF p_mode NOT IN ('batch', 'retry') THEN
|
|
118
|
+
RAISE EXCEPTION 'embedder_workflow mode must be batch or retry';
|
|
119
|
+
END IF;
|
|
120
|
+
|
|
121
|
+
v_url := df.getvar('hz_{{SCHEMA}}_url');
|
|
122
|
+
v_model := df.getvar('hz_{{SCHEMA}}_model');
|
|
123
|
+
v_key := df.getvar('hz_{{SCHEMA}}_key');
|
|
124
|
+
v_keyhdr := coalesce(df.getvar('hz_{{SCHEMA}}_keyhdr'), 'api-key');
|
|
125
|
+
v_bearer := coalesce(df.getvar('hz_{{SCHEMA}}_bearer'), 'false');
|
|
126
|
+
v_inputfield := coalesce(df.getvar('hz_{{SCHEMA}}_inputfield'), 'input');
|
|
127
|
+
v_timeout := coalesce(df.getvar('hz_{{SCHEMA}}_timeout'), '30')::int;
|
|
128
|
+
|
|
129
|
+
IF v_url IS NULL OR v_model IS NULL THEN
|
|
130
|
+
RAISE EXCEPTION 'embedder not configured: call configureEmbedder first';
|
|
131
|
+
END IF;
|
|
132
|
+
|
|
133
|
+
v_headers := jsonb_build_object('content-type', 'application/json');
|
|
134
|
+
IF v_key IS NOT NULL AND v_key <> '' THEN
|
|
135
|
+
v_headers := v_headers || jsonb_build_object(
|
|
136
|
+
v_keyhdr,
|
|
137
|
+
CASE WHEN v_bearer = 'true' THEN 'Bearer ' || v_key ELSE v_key END
|
|
138
|
+
);
|
|
139
|
+
END IF;
|
|
140
|
+
|
|
141
|
+
v_alias := p_mode;
|
|
142
|
+
v_resp_alias := CASE WHEN p_mode = 'batch' THEN 'resp' ELSE 'retry_resp' END;
|
|
143
|
+
v_limit := CASE WHEN p_mode = 'batch' THEN greatest(p_batch, 1) ELSE 1 END;
|
|
144
|
+
|
|
145
|
+
v_select_sql := format(
|
|
146
|
+
$q$SELECT jsonb_build_object(%L, jsonb_agg(t.txt ORDER BY t.id), 'model', %L)::text AS body,
|
|
147
|
+
array_agg(t.id ORDER BY t.id)::text AS ids,
|
|
148
|
+
array_agg(t.updated_at ORDER BY t.id)::text AS updated_ats,
|
|
149
|
+
bool_or(t.input_chars > 8000)::text AS has_oversized
|
|
150
|
+
FROM (SELECT id, updated_at,
|
|
151
|
+
coalesce(key, '') || E'\n' || coalesce(value::text, '') AS txt,
|
|
152
|
+
char_length(coalesce(key, '') || E'\n' || coalesce(value::text, '')) AS input_chars
|
|
153
|
+
FROM "{{SCHEMA}}".facts
|
|
154
|
+
WHERE %s
|
|
155
|
+
ORDER BY id
|
|
156
|
+
LIMIT %s) t
|
|
157
|
+
HAVING count(*) > 0$q$,
|
|
158
|
+
v_inputfield,
|
|
159
|
+
v_model,
|
|
160
|
+
CASE WHEN p_mode = 'batch'
|
|
161
|
+
THEN format('last_embed_error IS NULL AND (embedding IS NULL OR embedding_model IS DISTINCT FROM %L)', v_model)
|
|
162
|
+
ELSE 'last_embed_error = -1'
|
|
163
|
+
END,
|
|
164
|
+
v_limit);
|
|
165
|
+
|
|
166
|
+
v_too_large_sql := format($q$SELECT 1 WHERE coalesce(($%s.has_oversized)::boolean, false)$q$, v_alias);
|
|
167
|
+
|
|
168
|
+
v_resp_ok_sql := format(
|
|
169
|
+
$q$SELECT 1 WHERE "{{SCHEMA}}".embed_response_data_count($%s::jsonb) = cardinality(($%s.ids)::bigint[])$q$,
|
|
170
|
+
v_resp_alias,
|
|
171
|
+
v_alias);
|
|
172
|
+
|
|
173
|
+
v_batch_fail_sql := format(
|
|
174
|
+
$q$WITH selected AS (
|
|
175
|
+
SELECT u.id, u.seen_updated_at
|
|
176
|
+
FROM unnest(($%s.ids)::bigint[], ($%s.updated_ats)::timestamptz[]) AS u(id, seen_updated_at)
|
|
177
|
+
)
|
|
178
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
179
|
+
SET last_embed_error = -1,
|
|
180
|
+
embedding = NULL,
|
|
181
|
+
embedding_model = NULL
|
|
182
|
+
FROM selected s
|
|
183
|
+
WHERE f.id = s.id
|
|
184
|
+
AND f.updated_at IS NOT DISTINCT FROM s.seen_updated_at
|
|
185
|
+
AND f.last_embed_error IS NULL$q$,
|
|
186
|
+
v_alias,
|
|
187
|
+
v_alias);
|
|
188
|
+
|
|
189
|
+
v_zip_sql := format(
|
|
190
|
+
$q$WITH resp AS (SELECT ($%s::jsonb->>'body')::jsonb AS j),
|
|
191
|
+
emb AS (SELECT v.ord, v.e->'embedding' AS vec
|
|
192
|
+
FROM resp, jsonb_array_elements(resp.j->'data') WITH ORDINALITY AS v(e, ord)),
|
|
193
|
+
tgt AS (SELECT u.id, u.seen_updated_at, u.ord
|
|
194
|
+
FROM unnest(($%s.ids)::bigint[], ($%s.updated_ats)::timestamptz[]) WITH ORDINALITY AS u(id, seen_updated_at, ord)),
|
|
195
|
+
upd AS (
|
|
196
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
197
|
+
SET embedding = (emb.vec)::text::vector,
|
|
198
|
+
embedding_model = %L,
|
|
199
|
+
last_embed_error = NULL
|
|
200
|
+
FROM emb JOIN tgt ON tgt.ord = emb.ord
|
|
201
|
+
WHERE f.id = tgt.id
|
|
202
|
+
AND f.updated_at IS NOT DISTINCT FROM tgt.seen_updated_at
|
|
203
|
+
RETURNING f.id)
|
|
204
|
+
SELECT count(*) FROM upd$q$,
|
|
205
|
+
v_resp_alias,
|
|
206
|
+
v_alias,
|
|
207
|
+
v_alias,
|
|
208
|
+
v_model);
|
|
209
|
+
|
|
210
|
+
v_retry_fail_sql := format(
|
|
211
|
+
$q$WITH resp AS (
|
|
212
|
+
SELECT $%s::jsonb AS r
|
|
213
|
+
), tgt AS (
|
|
214
|
+
SELECT u.id, u.seen_updated_at
|
|
215
|
+
FROM unnest(($%s.ids)::bigint[], ($%s.updated_ats)::timestamptz[]) AS u(id, seen_updated_at)
|
|
216
|
+
), classified AS (
|
|
217
|
+
SELECT tgt.id, tgt.seen_updated_at,
|
|
218
|
+
"{{SCHEMA}}".embed_response_terminal_code(resp.r) AS code
|
|
219
|
+
FROM tgt, resp
|
|
220
|
+
), upd AS (
|
|
221
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
222
|
+
SET last_embed_error = classified.code,
|
|
223
|
+
embedding = NULL,
|
|
224
|
+
embedding_model = NULL
|
|
225
|
+
FROM classified
|
|
226
|
+
WHERE f.id = classified.id
|
|
227
|
+
AND f.updated_at IS NOT DISTINCT FROM classified.seen_updated_at
|
|
228
|
+
RETURNING f.id)
|
|
229
|
+
SELECT count(*) FROM upd$q$,
|
|
230
|
+
v_resp_alias,
|
|
231
|
+
v_alias,
|
|
232
|
+
v_alias);
|
|
233
|
+
|
|
234
|
+
v_oversized_fail_sql := format(
|
|
235
|
+
$q$WITH tgt AS (
|
|
236
|
+
SELECT u.id, u.seen_updated_at
|
|
237
|
+
FROM unnest(($%s.ids)::bigint[], ($%s.updated_ats)::timestamptz[]) AS u(id, seen_updated_at)
|
|
238
|
+
), upd AS (
|
|
239
|
+
UPDATE "{{SCHEMA}}".facts f
|
|
240
|
+
SET last_embed_error = 1001,
|
|
241
|
+
embedding = NULL,
|
|
242
|
+
embedding_model = NULL
|
|
243
|
+
FROM tgt
|
|
244
|
+
WHERE f.id = tgt.id
|
|
245
|
+
AND f.updated_at IS NOT DISTINCT FROM tgt.seen_updated_at
|
|
246
|
+
RETURNING f.id)
|
|
247
|
+
SELECT count(*) FROM upd$q$,
|
|
248
|
+
v_alias,
|
|
249
|
+
v_alias);
|
|
250
|
+
|
|
251
|
+
RETURN df.loop(
|
|
252
|
+
df.seq(
|
|
253
|
+
df.sleep(greatest(p_interval, 1)::bigint),
|
|
254
|
+
df.seq(
|
|
255
|
+
df.as(df.sql(v_select_sql), v_alias),
|
|
256
|
+
df.if_rows(v_alias,
|
|
257
|
+
df.seq(
|
|
258
|
+
df.as(df.sql(v_too_large_sql), v_alias || '_too_large'),
|
|
259
|
+
df.if_rows(v_alias || '_too_large',
|
|
260
|
+
CASE WHEN p_mode = 'batch' THEN df.sql(v_batch_fail_sql) ELSE df.sql(v_oversized_fail_sql) END,
|
|
261
|
+
df.seq(
|
|
262
|
+
df.as(df.http(v_url, 'POST', '$' || v_alias || '.body', v_headers, v_timeout), v_resp_alias),
|
|
263
|
+
df.seq(
|
|
264
|
+
df.as(df.sql(v_resp_ok_sql), v_resp_alias || '_ok'),
|
|
265
|
+
df.if_rows(v_resp_alias || '_ok',
|
|
266
|
+
df.sql(v_zip_sql),
|
|
267
|
+
CASE WHEN p_mode = 'batch' THEN df.sql(v_batch_fail_sql) ELSE df.sql(v_retry_fail_sql) END))))),
|
|
268
|
+
df.sql('SELECT 1')))),
|
|
269
|
+
NULL);
|
|
270
|
+
END $fn$;
|
|
271
|
+
|
|
272
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".embedder_batch_workflow(INT, INT);
|
|
273
|
+
DROP FUNCTION IF EXISTS "{{SCHEMA}}".embedder_retry_workflow(INT);
|