remdb 0.3.242__py3-none-any.whl
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.
Potentially problematic release.
This version of remdb might be problematic. Click here for more details.
- rem/__init__.py +129 -0
- rem/agentic/README.md +760 -0
- rem/agentic/__init__.py +54 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +38 -0
- rem/agentic/agents/agent_manager.py +311 -0
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +425 -0
- rem/agentic/context_builder.py +360 -0
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +273 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +240 -0
- rem/agentic/providers/phoenix.py +926 -0
- rem/agentic/providers/pydantic_ai.py +854 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +737 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +242 -0
- rem/api/README.md +657 -0
- rem/api/deps.py +253 -0
- rem/api/main.py +460 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +820 -0
- rem/api/mcp_router/server.py +243 -0
- rem/api/mcp_router/tools.py +1605 -0
- rem/api/middleware/tracking.py +172 -0
- rem/api/routers/admin.py +520 -0
- rem/api/routers/auth.py +898 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/child_streaming.py +394 -0
- rem/api/routers/chat/completions.py +702 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +202 -0
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +546 -0
- rem/api/routers/chat/streaming.py +950 -0
- rem/api/routers/chat/streaming_utils.py +327 -0
- rem/api/routers/common.py +18 -0
- rem/api/routers/dev.py +87 -0
- rem/api/routers/feedback.py +276 -0
- rem/api/routers/messages.py +620 -0
- rem/api/routers/models.py +86 -0
- rem/api/routers/query.py +362 -0
- rem/api/routers/shared_sessions.py +422 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +36 -0
- rem/auth/jwt.py +367 -0
- rem/auth/middleware.py +318 -0
- rem/auth/providers/__init__.py +16 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/email.py +215 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +517 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +299 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +549 -0
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +495 -0
- rem/cli/commands/db.py +828 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1698 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +388 -0
- rem/cli/commands/query.py +109 -0
- rem/cli/commands/scaffold.py +47 -0
- rem/cli/commands/schema.py +230 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/commands/session.py +453 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +123 -0
- rem/config.py +244 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +70 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +672 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +246 -0
- rem/models/entities/__init__.py +68 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +64 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +181 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/session.py +84 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/subscriber.py +175 -0
- rem/models/entities/user.py +93 -0
- rem/py.typed +0 -0
- rem/registry.py +373 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/agent-builder.yaml +235 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +132 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +18 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +760 -0
- rem/services/content/service.py +762 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +322 -0
- rem/services/dreaming/moment_service.py +251 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/email/__init__.py +10 -0
- rem/services/email/service.py +522 -0
- rem/services/email/templates.py +360 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +127 -0
- rem/services/embeddings/worker.py +435 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +960 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +757 -0
- rem/services/postgres/__init__.py +49 -0
- rem/services/postgres/diff_service.py +599 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/programmable_diff_service.py +635 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
- rem/services/postgres/register_type.py +353 -0
- rem/services/postgres/repository.py +481 -0
- rem/services/postgres/schema_generator.py +661 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +355 -0
- rem/services/rate_limit.py +113 -0
- rem/services/rem/README.md +318 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +180 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +608 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +13 -0
- rem/services/session/compression.py +488 -0
- rem/services/session/pydantic_messages.py +310 -0
- rem/services/session/reload.py +85 -0
- rem/services/user_service.py +130 -0
- rem/settings.py +1877 -0
- rem/sql/background_indexes.sql +52 -0
- rem/sql/migrations/001_install.sql +983 -0
- rem/sql/migrations/002_install_models.sql +3157 -0
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +282 -0
- rem/sql/migrations/005_schema_update.sql +145 -0
- rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +628 -0
- rem/utils/__init__.py +61 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/constants.py +97 -0
- rem/utils/date_utils.py +228 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +436 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/files.py +323 -0
- rem/utils/markdown.py +16 -0
- rem/utils/mime_types.py +158 -0
- rem/utils/model_helpers.py +492 -0
- rem/utils/schema_loader.py +649 -0
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +350 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +325 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +7 -0
- rem/workers/db_listener.py +579 -0
- rem/workers/db_maintainer.py +74 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- rem/workers/unlogged_maintainer.py +463 -0
- remdb-0.3.242.dist-info/METADATA +1632 -0
- remdb-0.3.242.dist-info/RECORD +235 -0
- remdb-0.3.242.dist-info/WHEEL +4 -0
- remdb-0.3.242.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
-- REM Optional Extensions
|
|
2
|
+
-- Description: Optional PostgreSQL extensions that enhance functionality but are not required
|
|
3
|
+
-- Version: 1.0.0
|
|
4
|
+
-- Date: 2025-11-29
|
|
5
|
+
--
|
|
6
|
+
-- These extensions are installed with try/catch - failures are logged but don't break the install.
|
|
7
|
+
-- This allows the same migration to work on:
|
|
8
|
+
-- - Custom images with extensions baked in (percolationlabs/rem-pg:18)
|
|
9
|
+
-- - Standard PostgreSQL images (extensions will be skipped)
|
|
10
|
+
--
|
|
11
|
+
-- Extensions:
|
|
12
|
+
-- - pg_net: Async HTTP/HTTPS requests from triggers and functions (Supabase)
|
|
13
|
+
|
|
14
|
+
-- ============================================================================
|
|
15
|
+
-- pg_net: Async HTTP Extension
|
|
16
|
+
-- ============================================================================
|
|
17
|
+
-- Enables PostgreSQL to make non-blocking HTTP requests from triggers and functions.
|
|
18
|
+
-- Requires: Custom image with pg_net compiled, shared_preload_libraries='pg_net'
|
|
19
|
+
--
|
|
20
|
+
-- Use cases:
|
|
21
|
+
-- - Webhook notifications on data changes
|
|
22
|
+
-- - Async event publishing to external APIs
|
|
23
|
+
-- - Background HTTP requests from triggers
|
|
24
|
+
|
|
25
|
+
DO $$
|
|
26
|
+
BEGIN
|
|
27
|
+
-- Attempt to create pg_net extension
|
|
28
|
+
CREATE EXTENSION IF NOT EXISTS pg_net;
|
|
29
|
+
RAISE NOTICE ' pg_net extension installed successfully';
|
|
30
|
+
EXCEPTION
|
|
31
|
+
WHEN OTHERS THEN
|
|
32
|
+
RAISE NOTICE ' pg_net extension not available (this is OK if using standard PostgreSQL image)';
|
|
33
|
+
RAISE NOTICE ' Error: %', SQLERRM;
|
|
34
|
+
END $$;
|
|
35
|
+
|
|
36
|
+
-- ============================================================================
|
|
37
|
+
-- pg_net Helper Functions (only created if extension exists)
|
|
38
|
+
-- ============================================================================
|
|
39
|
+
-- Wrapper functions for common HTTP operations with sensible defaults
|
|
40
|
+
|
|
41
|
+
DO $$
|
|
42
|
+
BEGIN
|
|
43
|
+
-- Only create helpers if pg_net is available
|
|
44
|
+
IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net') THEN
|
|
45
|
+
|
|
46
|
+
-- Helper: POST JSON to a URL with standard headers
|
|
47
|
+
EXECUTE $func$
|
|
48
|
+
CREATE OR REPLACE FUNCTION rem_http_post(
|
|
49
|
+
p_url TEXT,
|
|
50
|
+
p_body JSONB,
|
|
51
|
+
p_headers JSONB DEFAULT '{}'::jsonb
|
|
52
|
+
)
|
|
53
|
+
RETURNS BIGINT AS $inner$
|
|
54
|
+
DECLARE
|
|
55
|
+
merged_headers JSONB;
|
|
56
|
+
request_id BIGINT;
|
|
57
|
+
BEGIN
|
|
58
|
+
-- Merge default headers with provided headers
|
|
59
|
+
merged_headers := '{"Content-Type": "application/json"}'::jsonb || p_headers;
|
|
60
|
+
|
|
61
|
+
SELECT net.http_post(
|
|
62
|
+
url := p_url,
|
|
63
|
+
headers := merged_headers,
|
|
64
|
+
body := p_body
|
|
65
|
+
) INTO request_id;
|
|
66
|
+
|
|
67
|
+
RETURN request_id;
|
|
68
|
+
END;
|
|
69
|
+
$inner$ LANGUAGE plpgsql;
|
|
70
|
+
$func$;
|
|
71
|
+
|
|
72
|
+
RAISE NOTICE ' rem_http_post helper function created';
|
|
73
|
+
|
|
74
|
+
-- Helper: GET from a URL
|
|
75
|
+
EXECUTE $func$
|
|
76
|
+
CREATE OR REPLACE FUNCTION rem_http_get(
|
|
77
|
+
p_url TEXT,
|
|
78
|
+
p_headers JSONB DEFAULT '{}'::jsonb
|
|
79
|
+
)
|
|
80
|
+
RETURNS BIGINT AS $inner$
|
|
81
|
+
DECLARE
|
|
82
|
+
request_id BIGINT;
|
|
83
|
+
BEGIN
|
|
84
|
+
SELECT net.http_get(
|
|
85
|
+
url := p_url,
|
|
86
|
+
headers := p_headers
|
|
87
|
+
) INTO request_id;
|
|
88
|
+
|
|
89
|
+
RETURN request_id;
|
|
90
|
+
END;
|
|
91
|
+
$inner$ LANGUAGE plpgsql;
|
|
92
|
+
$func$;
|
|
93
|
+
|
|
94
|
+
RAISE NOTICE ' rem_http_get helper function created';
|
|
95
|
+
|
|
96
|
+
-- ====================================================================
|
|
97
|
+
-- REM Query Function
|
|
98
|
+
-- ====================================================================
|
|
99
|
+
-- Executes REM queries via the REM API using pg_net
|
|
100
|
+
--
|
|
101
|
+
-- Default API host: rem-api (works in K8s same namespace)
|
|
102
|
+
-- For local Docker testing: Add "host.docker.internal rem-api" to /etc/hosts
|
|
103
|
+
-- Or override with p_api_host parameter
|
|
104
|
+
--
|
|
105
|
+
-- Example:
|
|
106
|
+
-- SELECT rem_query('LOOKUP sarah-chen', 'user123');
|
|
107
|
+
-- SELECT rem_query('SEARCH resources ''API design'' LIMIT 5', 'user123');
|
|
108
|
+
|
|
109
|
+
EXECUTE $func$
|
|
110
|
+
CREATE OR REPLACE FUNCTION rem_query(
|
|
111
|
+
p_query TEXT,
|
|
112
|
+
p_user_id TEXT,
|
|
113
|
+
p_api_host TEXT DEFAULT 'rem-api',
|
|
114
|
+
p_api_port INTEGER DEFAULT 8000,
|
|
115
|
+
p_mode TEXT DEFAULT 'rem-dialect'
|
|
116
|
+
)
|
|
117
|
+
RETURNS BIGINT AS $inner$
|
|
118
|
+
DECLARE
|
|
119
|
+
api_url TEXT;
|
|
120
|
+
request_body JSONB;
|
|
121
|
+
request_headers JSONB;
|
|
122
|
+
request_id BIGINT;
|
|
123
|
+
BEGIN
|
|
124
|
+
-- Build API URL
|
|
125
|
+
-- Default: http://rem-api:8000/api/v1/query (K8s same namespace)
|
|
126
|
+
api_url := format('http://%s:%s/api/v1/query', p_api_host, p_api_port);
|
|
127
|
+
|
|
128
|
+
-- Build request body
|
|
129
|
+
request_body := jsonb_build_object(
|
|
130
|
+
'query', p_query,
|
|
131
|
+
'mode', p_mode
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
-- Build headers with user ID
|
|
135
|
+
request_headers := jsonb_build_object(
|
|
136
|
+
'Content-Type', 'application/json',
|
|
137
|
+
'X-User-Id', p_user_id
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
-- Make async HTTP POST request
|
|
141
|
+
SELECT net.http_post(
|
|
142
|
+
url := api_url,
|
|
143
|
+
headers := request_headers,
|
|
144
|
+
body := request_body
|
|
145
|
+
) INTO request_id;
|
|
146
|
+
|
|
147
|
+
RETURN request_id;
|
|
148
|
+
END;
|
|
149
|
+
$inner$ LANGUAGE plpgsql;
|
|
150
|
+
$func$;
|
|
151
|
+
|
|
152
|
+
RAISE NOTICE ' rem_query() function created';
|
|
153
|
+
|
|
154
|
+
-- Helper to get query results (waits for async response)
|
|
155
|
+
-- NOTE: pg_net is async by design. This function polls for the response.
|
|
156
|
+
-- For best results, use rem_query() and check results later, or use longer timeouts.
|
|
157
|
+
EXECUTE $func$
|
|
158
|
+
CREATE OR REPLACE FUNCTION rem_query_result(
|
|
159
|
+
p_request_id BIGINT,
|
|
160
|
+
p_timeout_ms INTEGER DEFAULT 10000
|
|
161
|
+
)
|
|
162
|
+
RETURNS JSONB AS $inner$
|
|
163
|
+
DECLARE
|
|
164
|
+
v_status_code INTEGER;
|
|
165
|
+
v_content TEXT;
|
|
166
|
+
v_found BOOLEAN;
|
|
167
|
+
start_time TIMESTAMP;
|
|
168
|
+
elapsed_ms INTEGER;
|
|
169
|
+
BEGIN
|
|
170
|
+
start_time := clock_timestamp();
|
|
171
|
+
|
|
172
|
+
-- Poll for response with timeout
|
|
173
|
+
-- Each iteration starts a new query to see committed data from background worker
|
|
174
|
+
LOOP
|
|
175
|
+
-- Check if response exists (background worker commits independently)
|
|
176
|
+
SELECT true, status_code, content::text
|
|
177
|
+
INTO v_found, v_status_code, v_content
|
|
178
|
+
FROM net._http_response
|
|
179
|
+
WHERE id = p_request_id;
|
|
180
|
+
|
|
181
|
+
-- Found response
|
|
182
|
+
IF v_found THEN
|
|
183
|
+
IF v_status_code = 200 THEN
|
|
184
|
+
RETURN v_content::jsonb;
|
|
185
|
+
ELSE
|
|
186
|
+
RETURN jsonb_build_object(
|
|
187
|
+
'error', true,
|
|
188
|
+
'status_code', v_status_code,
|
|
189
|
+
'content', v_content
|
|
190
|
+
);
|
|
191
|
+
END IF;
|
|
192
|
+
END IF;
|
|
193
|
+
|
|
194
|
+
-- Check timeout
|
|
195
|
+
elapsed_ms := EXTRACT(EPOCH FROM (clock_timestamp() - start_time)) * 1000;
|
|
196
|
+
IF elapsed_ms >= p_timeout_ms THEN
|
|
197
|
+
RETURN jsonb_build_object(
|
|
198
|
+
'error', true,
|
|
199
|
+
'message', 'Request timeout - pg_net is async, response may arrive later',
|
|
200
|
+
'request_id', p_request_id,
|
|
201
|
+
'hint', 'Check net._http_response table or increase timeout'
|
|
202
|
+
);
|
|
203
|
+
END IF;
|
|
204
|
+
|
|
205
|
+
-- Wait 500ms before next poll (pg_net worker runs every 100ms)
|
|
206
|
+
PERFORM pg_sleep(0.5);
|
|
207
|
+
END LOOP;
|
|
208
|
+
END;
|
|
209
|
+
$inner$ LANGUAGE plpgsql;
|
|
210
|
+
$func$;
|
|
211
|
+
|
|
212
|
+
RAISE NOTICE ' rem_query_result() function created';
|
|
213
|
+
|
|
214
|
+
-- Convenience function: execute query and wait for result
|
|
215
|
+
-- WARNING: Due to PostgreSQL transaction isolation, this may timeout even when
|
|
216
|
+
-- the request succeeds. The background worker commits separately and the polling
|
|
217
|
+
-- loop may not see the response. Use rem_query() + check net._http_response for
|
|
218
|
+
-- more reliable async operation.
|
|
219
|
+
EXECUTE $func$
|
|
220
|
+
CREATE OR REPLACE FUNCTION rem_query_sync(
|
|
221
|
+
p_query TEXT,
|
|
222
|
+
p_user_id TEXT,
|
|
223
|
+
p_api_host TEXT DEFAULT 'rem-api',
|
|
224
|
+
p_api_port INTEGER DEFAULT 8000,
|
|
225
|
+
p_mode TEXT DEFAULT 'rem-dialect',
|
|
226
|
+
p_timeout_ms INTEGER DEFAULT 10000
|
|
227
|
+
)
|
|
228
|
+
RETURNS JSONB AS $inner$
|
|
229
|
+
DECLARE
|
|
230
|
+
request_id BIGINT;
|
|
231
|
+
v_status_code INTEGER;
|
|
232
|
+
v_content TEXT;
|
|
233
|
+
v_found BOOLEAN := false;
|
|
234
|
+
start_time TIMESTAMP;
|
|
235
|
+
elapsed_ms INTEGER;
|
|
236
|
+
BEGIN
|
|
237
|
+
-- Execute query - this queues the HTTP request
|
|
238
|
+
request_id := rem_query(p_query, p_user_id, p_api_host, p_api_port, p_mode);
|
|
239
|
+
|
|
240
|
+
-- Wait for response with explicit snapshot refresh attempts
|
|
241
|
+
start_time := clock_timestamp();
|
|
242
|
+
LOOP
|
|
243
|
+
-- Query in separate subtransaction-like context
|
|
244
|
+
SELECT true, status_code, content::text
|
|
245
|
+
INTO v_found, v_status_code, v_content
|
|
246
|
+
FROM net._http_response
|
|
247
|
+
WHERE id = request_id;
|
|
248
|
+
|
|
249
|
+
IF v_found THEN
|
|
250
|
+
IF v_status_code = 200 THEN
|
|
251
|
+
RETURN v_content::jsonb;
|
|
252
|
+
ELSE
|
|
253
|
+
RETURN jsonb_build_object('error', true, 'status_code', v_status_code, 'content', v_content);
|
|
254
|
+
END IF;
|
|
255
|
+
END IF;
|
|
256
|
+
|
|
257
|
+
elapsed_ms := EXTRACT(EPOCH FROM (clock_timestamp() - start_time)) * 1000;
|
|
258
|
+
IF elapsed_ms >= p_timeout_ms THEN
|
|
259
|
+
-- Return info about the async request so caller can check later
|
|
260
|
+
RETURN jsonb_build_object(
|
|
261
|
+
'pending', true,
|
|
262
|
+
'request_id', request_id,
|
|
263
|
+
'message', 'Request queued but response not yet visible due to transaction isolation',
|
|
264
|
+
'hint', 'Query net._http_response WHERE id = ' || request_id || ' after this transaction commits'
|
|
265
|
+
);
|
|
266
|
+
END IF;
|
|
267
|
+
|
|
268
|
+
PERFORM pg_sleep(0.3);
|
|
269
|
+
END LOOP;
|
|
270
|
+
END;
|
|
271
|
+
$inner$ LANGUAGE plpgsql;
|
|
272
|
+
$func$;
|
|
273
|
+
|
|
274
|
+
RAISE NOTICE ' rem_query_sync() function created (async pattern recommended)';
|
|
275
|
+
|
|
276
|
+
ELSE
|
|
277
|
+
RAISE NOTICE ' Skipping pg_net helper functions (extension not installed)';
|
|
278
|
+
END IF;
|
|
279
|
+
END $$;
|
|
280
|
+
|
|
281
|
+
-- ============================================================================
|
|
282
|
+
-- RECORD INSTALLATION
|
|
283
|
+
-- ============================================================================
|
|
284
|
+
|
|
285
|
+
DO $$
|
|
286
|
+
BEGIN
|
|
287
|
+
-- Only record if migrations table exists
|
|
288
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rem_migrations') THEN
|
|
289
|
+
INSERT INTO rem_migrations (name, type, version)
|
|
290
|
+
VALUES ('003_optional_extensions.sql', 'install', '1.0.0')
|
|
291
|
+
ON CONFLICT (name) DO UPDATE
|
|
292
|
+
SET applied_at = CURRENT_TIMESTAMP,
|
|
293
|
+
applied_by = CURRENT_USER;
|
|
294
|
+
END IF;
|
|
295
|
+
END $$;
|
|
296
|
+
|
|
297
|
+
-- ============================================================================
|
|
298
|
+
-- COMPLETION
|
|
299
|
+
-- ============================================================================
|
|
300
|
+
|
|
301
|
+
DO $$
|
|
302
|
+
DECLARE
|
|
303
|
+
pg_net_installed BOOLEAN;
|
|
304
|
+
BEGIN
|
|
305
|
+
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net') INTO pg_net_installed;
|
|
306
|
+
|
|
307
|
+
RAISE NOTICE '============================================================';
|
|
308
|
+
RAISE NOTICE 'Optional Extensions Installation Complete';
|
|
309
|
+
RAISE NOTICE '============================================================';
|
|
310
|
+
RAISE NOTICE '';
|
|
311
|
+
IF pg_net_installed THEN
|
|
312
|
+
RAISE NOTICE 'Installed:';
|
|
313
|
+
RAISE NOTICE ' pg_net (async HTTP/HTTPS requests)';
|
|
314
|
+
RAISE NOTICE ' rem_http_post() - POST JSON to URL';
|
|
315
|
+
RAISE NOTICE ' rem_http_get() - GET from URL';
|
|
316
|
+
RAISE NOTICE ' rem_query() - Execute REM query (async)';
|
|
317
|
+
RAISE NOTICE ' rem_query_result() - Get async query result';
|
|
318
|
+
RAISE NOTICE ' rem_query_sync() - Execute and wait for result';
|
|
319
|
+
ELSE
|
|
320
|
+
RAISE NOTICE 'Skipped (not available in this PostgreSQL image):';
|
|
321
|
+
RAISE NOTICE ' pg_net';
|
|
322
|
+
RAISE NOTICE '';
|
|
323
|
+
RAISE NOTICE 'To enable pg_net, use the custom image: percolationlabs/rem-pg:18';
|
|
324
|
+
END IF;
|
|
325
|
+
RAISE NOTICE '============================================================';
|
|
326
|
+
END $$;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
-- REM Cache System
|
|
2
|
+
-- Description: Cache management helpers for UNLOGGED tables (kv_store)
|
|
3
|
+
-- Version: 1.0.0
|
|
4
|
+
-- Date: 2025-11-29
|
|
5
|
+
--
|
|
6
|
+
-- This migration adds:
|
|
7
|
+
-- 1. cache_system_state table for debouncing and API secret storage
|
|
8
|
+
-- 2. maybe_trigger_kv_rebuild() function for async rebuild triggering
|
|
9
|
+
-- 3. Helper functions for cache management
|
|
10
|
+
--
|
|
11
|
+
-- NOTE: Core functions (rem_lookup, rem_fuzzy, rem_traverse) are defined in 001_install.sql
|
|
12
|
+
-- This file only provides cache-specific infrastructure.
|
|
13
|
+
|
|
14
|
+
-- ============================================================================
|
|
15
|
+
-- REQUIRED EXTENSION
|
|
16
|
+
-- ============================================================================
|
|
17
|
+
-- pgcrypto is needed for gen_random_bytes() to generate API secrets
|
|
18
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
19
|
+
|
|
20
|
+
-- ============================================================================
|
|
21
|
+
-- CACHE SYSTEM STATE TABLE
|
|
22
|
+
-- ============================================================================
|
|
23
|
+
-- Stores:
|
|
24
|
+
-- - Last rebuild trigger timestamp (for debouncing)
|
|
25
|
+
-- - API secret for internal endpoint authentication
|
|
26
|
+
-- - Rebuild statistics
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS cache_system_state (
|
|
29
|
+
id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1), -- Single row table
|
|
30
|
+
api_secret TEXT NOT NULL, -- Secret for internal API auth
|
|
31
|
+
last_triggered_at TIMESTAMPTZ, -- Debounce: last trigger time
|
|
32
|
+
last_rebuild_at TIMESTAMPTZ, -- Last successful rebuild
|
|
33
|
+
triggered_by TEXT, -- What triggered last rebuild
|
|
34
|
+
trigger_count INTEGER DEFAULT 0, -- Total trigger count
|
|
35
|
+
rebuild_count INTEGER DEFAULT 0, -- Total successful rebuilds
|
|
36
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
37
|
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- Generate initial secret if table is empty
|
|
41
|
+
INSERT INTO cache_system_state (id, api_secret)
|
|
42
|
+
SELECT 1, encode(gen_random_bytes(32), 'hex')
|
|
43
|
+
WHERE NOT EXISTS (SELECT 1 FROM cache_system_state WHERE id = 1);
|
|
44
|
+
|
|
45
|
+
COMMENT ON TABLE cache_system_state IS
|
|
46
|
+
'Single-row table storing cache system state: API secret for internal auth and debounce tracking';
|
|
47
|
+
|
|
48
|
+
-- ============================================================================
|
|
49
|
+
-- HELPER: Check if extension exists
|
|
50
|
+
-- ============================================================================
|
|
51
|
+
|
|
52
|
+
CREATE OR REPLACE FUNCTION rem_extension_exists(p_extension TEXT)
|
|
53
|
+
RETURNS BOOLEAN AS $$
|
|
54
|
+
BEGIN
|
|
55
|
+
RETURN EXISTS (SELECT 1 FROM pg_extension WHERE extname = p_extension);
|
|
56
|
+
END;
|
|
57
|
+
$$ LANGUAGE plpgsql STABLE;
|
|
58
|
+
|
|
59
|
+
-- ============================================================================
|
|
60
|
+
-- HELPER: Check if kv_store is empty for user
|
|
61
|
+
-- ============================================================================
|
|
62
|
+
|
|
63
|
+
CREATE OR REPLACE FUNCTION rem_kv_store_empty(p_user_id TEXT)
|
|
64
|
+
RETURNS BOOLEAN AS $$
|
|
65
|
+
BEGIN
|
|
66
|
+
-- Quick existence check - very fast with index
|
|
67
|
+
-- Check for user-specific OR public (NULL user_id) entries
|
|
68
|
+
-- This ensures self-healing triggers correctly for public ontologies
|
|
69
|
+
RETURN NOT EXISTS (
|
|
70
|
+
SELECT 1 FROM kv_store
|
|
71
|
+
WHERE user_id = p_user_id OR user_id IS NULL
|
|
72
|
+
LIMIT 1
|
|
73
|
+
);
|
|
74
|
+
END;
|
|
75
|
+
$$ LANGUAGE plpgsql STABLE;
|
|
76
|
+
|
|
77
|
+
-- ============================================================================
|
|
78
|
+
-- MAIN: Maybe trigger KV rebuild (async, non-blocking)
|
|
79
|
+
-- ============================================================================
|
|
80
|
+
-- Called when a query returns 0 results and kv_store appears empty.
|
|
81
|
+
-- Uses pg_net (if available) to call API, falls back to dblink.
|
|
82
|
+
-- Includes debouncing to prevent request storms.
|
|
83
|
+
|
|
84
|
+
CREATE OR REPLACE FUNCTION maybe_trigger_kv_rebuild(
|
|
85
|
+
p_user_id TEXT,
|
|
86
|
+
p_triggered_by TEXT DEFAULT 'query'
|
|
87
|
+
)
|
|
88
|
+
RETURNS VOID AS $$
|
|
89
|
+
DECLARE
|
|
90
|
+
v_has_pgnet BOOLEAN;
|
|
91
|
+
v_has_dblink BOOLEAN;
|
|
92
|
+
v_last_trigger TIMESTAMPTZ;
|
|
93
|
+
v_api_secret TEXT;
|
|
94
|
+
v_debounce_seconds CONSTANT INTEGER := 30;
|
|
95
|
+
v_api_url TEXT := 'http://rem-api.rem.svc.cluster.local:8000/api/admin/internal/rebuild-kv';
|
|
96
|
+
v_request_id BIGINT;
|
|
97
|
+
BEGIN
|
|
98
|
+
-- Quick check: is kv_store actually empty for this user?
|
|
99
|
+
IF NOT rem_kv_store_empty(p_user_id) THEN
|
|
100
|
+
RETURN; -- Cache has data, nothing to do
|
|
101
|
+
END IF;
|
|
102
|
+
|
|
103
|
+
-- Try to acquire advisory lock (non-blocking, transaction-scoped)
|
|
104
|
+
-- This prevents multiple concurrent triggers
|
|
105
|
+
IF NOT pg_try_advisory_xact_lock(2147483646) THEN
|
|
106
|
+
RETURN; -- Another session is handling it
|
|
107
|
+
END IF;
|
|
108
|
+
|
|
109
|
+
-- Check debounce: was rebuild triggered recently?
|
|
110
|
+
SELECT last_triggered_at, api_secret
|
|
111
|
+
INTO v_last_trigger, v_api_secret
|
|
112
|
+
FROM cache_system_state
|
|
113
|
+
WHERE id = 1;
|
|
114
|
+
|
|
115
|
+
IF v_last_trigger IS NOT NULL
|
|
116
|
+
AND v_last_trigger > (CURRENT_TIMESTAMP - (v_debounce_seconds || ' seconds')::INTERVAL) THEN
|
|
117
|
+
RETURN; -- Triggered recently, skip
|
|
118
|
+
END IF;
|
|
119
|
+
|
|
120
|
+
-- Update state (so concurrent callers see it)
|
|
121
|
+
UPDATE cache_system_state
|
|
122
|
+
SET last_triggered_at = CURRENT_TIMESTAMP,
|
|
123
|
+
triggered_by = p_triggered_by,
|
|
124
|
+
trigger_count = trigger_count + 1,
|
|
125
|
+
updated_at = CURRENT_TIMESTAMP
|
|
126
|
+
WHERE id = 1;
|
|
127
|
+
|
|
128
|
+
-- Check available extensions
|
|
129
|
+
v_has_pgnet := rem_extension_exists('pg_net');
|
|
130
|
+
v_has_dblink := rem_extension_exists('dblink');
|
|
131
|
+
|
|
132
|
+
-- Priority 1: pg_net (async HTTP to API - supports S3 restore)
|
|
133
|
+
IF v_has_pgnet THEN
|
|
134
|
+
BEGIN
|
|
135
|
+
SELECT net.http_post(
|
|
136
|
+
url := v_api_url,
|
|
137
|
+
headers := jsonb_build_object(
|
|
138
|
+
'Content-Type', 'application/json',
|
|
139
|
+
'X-Internal-Secret', v_api_secret
|
|
140
|
+
),
|
|
141
|
+
body := jsonb_build_object(
|
|
142
|
+
'user_id', p_user_id,
|
|
143
|
+
'triggered_by', 'pg_net_' || p_triggered_by,
|
|
144
|
+
'timestamp', CURRENT_TIMESTAMP
|
|
145
|
+
)
|
|
146
|
+
) INTO v_request_id;
|
|
147
|
+
|
|
148
|
+
RAISE DEBUG 'kv_rebuild triggered via pg_net (request_id: %)', v_request_id;
|
|
149
|
+
RETURN;
|
|
150
|
+
EXCEPTION WHEN OTHERS THEN
|
|
151
|
+
RAISE WARNING 'pg_net trigger failed: %, falling back to dblink', SQLERRM;
|
|
152
|
+
END;
|
|
153
|
+
END IF;
|
|
154
|
+
|
|
155
|
+
-- Priority 2: dblink (async SQL - direct rebuild)
|
|
156
|
+
IF v_has_dblink THEN
|
|
157
|
+
BEGIN
|
|
158
|
+
-- Connect to self (same database)
|
|
159
|
+
PERFORM dblink_connect(
|
|
160
|
+
'kv_rebuild_conn',
|
|
161
|
+
format('dbname=%s', current_database())
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
-- Send async query (returns immediately)
|
|
165
|
+
PERFORM dblink_send_query(
|
|
166
|
+
'kv_rebuild_conn',
|
|
167
|
+
'SELECT rebuild_kv_store()'
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
-- Don't disconnect - query continues in background
|
|
171
|
+
-- Connection auto-closes when session ends
|
|
172
|
+
|
|
173
|
+
RAISE DEBUG 'kv_rebuild triggered via dblink';
|
|
174
|
+
RETURN;
|
|
175
|
+
EXCEPTION WHEN OTHERS THEN
|
|
176
|
+
-- Clean up failed connection
|
|
177
|
+
BEGIN
|
|
178
|
+
PERFORM dblink_disconnect('kv_rebuild_conn');
|
|
179
|
+
EXCEPTION WHEN OTHERS THEN
|
|
180
|
+
NULL;
|
|
181
|
+
END;
|
|
182
|
+
RAISE WARNING 'dblink trigger failed: %', SQLERRM;
|
|
183
|
+
END;
|
|
184
|
+
END IF;
|
|
185
|
+
|
|
186
|
+
-- No async method available - log warning but don't block query
|
|
187
|
+
RAISE WARNING 'No async rebuild method available (pg_net or dblink). Cache rebuild skipped.';
|
|
188
|
+
|
|
189
|
+
EXCEPTION WHEN OTHERS THEN
|
|
190
|
+
-- Never fail the calling query
|
|
191
|
+
RAISE WARNING 'maybe_trigger_kv_rebuild failed: %', SQLERRM;
|
|
192
|
+
END;
|
|
193
|
+
$$ LANGUAGE plpgsql;
|
|
194
|
+
|
|
195
|
+
COMMENT ON FUNCTION maybe_trigger_kv_rebuild IS
|
|
196
|
+
'Async trigger for kv_store rebuild. Uses pg_net (API) or dblink (SQL). Includes debouncing.';
|
|
197
|
+
|
|
198
|
+
-- ============================================================================
|
|
199
|
+
-- HELPER: Get API secret for validation
|
|
200
|
+
-- ============================================================================
|
|
201
|
+
|
|
202
|
+
CREATE OR REPLACE FUNCTION rem_get_cache_api_secret()
|
|
203
|
+
RETURNS TEXT AS $$
|
|
204
|
+
DECLARE
|
|
205
|
+
v_secret TEXT;
|
|
206
|
+
BEGIN
|
|
207
|
+
SELECT api_secret INTO v_secret FROM cache_system_state WHERE id = 1;
|
|
208
|
+
RETURN v_secret;
|
|
209
|
+
END;
|
|
210
|
+
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
211
|
+
|
|
212
|
+
-- Only allow rem user to execute
|
|
213
|
+
REVOKE ALL ON FUNCTION rem_get_cache_api_secret() FROM PUBLIC;
|
|
214
|
+
|
|
215
|
+
-- ============================================================================
|
|
216
|
+
-- HELPER: Record successful rebuild
|
|
217
|
+
-- ============================================================================
|
|
218
|
+
|
|
219
|
+
CREATE OR REPLACE FUNCTION rem_record_cache_rebuild(p_triggered_by TEXT DEFAULT 'api')
|
|
220
|
+
RETURNS VOID AS $$
|
|
221
|
+
BEGIN
|
|
222
|
+
UPDATE cache_system_state
|
|
223
|
+
SET last_rebuild_at = CURRENT_TIMESTAMP,
|
|
224
|
+
rebuild_count = rebuild_count + 1,
|
|
225
|
+
updated_at = CURRENT_TIMESTAMP
|
|
226
|
+
WHERE id = 1;
|
|
227
|
+
END;
|
|
228
|
+
$$ LANGUAGE plpgsql;
|
|
229
|
+
|
|
230
|
+
-- ============================================================================
|
|
231
|
+
-- RECORD INSTALLATION
|
|
232
|
+
-- ============================================================================
|
|
233
|
+
|
|
234
|
+
DO $$
|
|
235
|
+
BEGIN
|
|
236
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rem_migrations') THEN
|
|
237
|
+
INSERT INTO rem_migrations (name, type, version)
|
|
238
|
+
VALUES ('004_cache_system.sql', 'install', '1.0.0')
|
|
239
|
+
ON CONFLICT (name) DO UPDATE
|
|
240
|
+
SET applied_at = CURRENT_TIMESTAMP,
|
|
241
|
+
applied_by = CURRENT_USER;
|
|
242
|
+
END IF;
|
|
243
|
+
END $$;
|
|
244
|
+
|
|
245
|
+
-- ============================================================================
|
|
246
|
+
-- COMPLETION
|
|
247
|
+
-- ============================================================================
|
|
248
|
+
|
|
249
|
+
DO $$
|
|
250
|
+
DECLARE
|
|
251
|
+
v_has_pgnet BOOLEAN;
|
|
252
|
+
v_has_dblink BOOLEAN;
|
|
253
|
+
BEGIN
|
|
254
|
+
v_has_pgnet := rem_extension_exists('pg_net');
|
|
255
|
+
v_has_dblink := rem_extension_exists('dblink');
|
|
256
|
+
|
|
257
|
+
RAISE NOTICE '============================================================';
|
|
258
|
+
RAISE NOTICE 'Cache System Installation Complete';
|
|
259
|
+
RAISE NOTICE '============================================================';
|
|
260
|
+
RAISE NOTICE '';
|
|
261
|
+
RAISE NOTICE 'Tables:';
|
|
262
|
+
RAISE NOTICE ' cache_system_state - Debounce tracking and API secret';
|
|
263
|
+
RAISE NOTICE '';
|
|
264
|
+
RAISE NOTICE 'Functions:';
|
|
265
|
+
RAISE NOTICE ' maybe_trigger_kv_rebuild() - Async rebuild trigger';
|
|
266
|
+
RAISE NOTICE ' rem_kv_store_empty() - Check if cache is empty';
|
|
267
|
+
RAISE NOTICE ' rem_get_cache_api_secret() - Get API secret';
|
|
268
|
+
RAISE NOTICE ' rem_record_cache_rebuild() - Record rebuild completion';
|
|
269
|
+
RAISE NOTICE '';
|
|
270
|
+
RAISE NOTICE 'Async Methods Available:';
|
|
271
|
+
IF v_has_pgnet THEN
|
|
272
|
+
RAISE NOTICE ' [x] pg_net - HTTP POST to API (preferred)';
|
|
273
|
+
ELSE
|
|
274
|
+
RAISE NOTICE ' [ ] pg_net - Not installed';
|
|
275
|
+
END IF;
|
|
276
|
+
IF v_has_dblink THEN
|
|
277
|
+
RAISE NOTICE ' [x] dblink - Async SQL (fallback)';
|
|
278
|
+
ELSE
|
|
279
|
+
RAISE NOTICE ' [ ] dblink - Not installed';
|
|
280
|
+
END IF;
|
|
281
|
+
RAISE NOTICE '============================================================';
|
|
282
|
+
END $$;
|