remdb 0.3.0__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 +2 -0
- rem/agentic/README.md +650 -0
- rem/agentic/__init__.py +39 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +8 -0
- rem/agentic/context.py +148 -0
- rem/agentic/context_builder.py +329 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +107 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +151 -0
- rem/agentic/providers/phoenix.py +674 -0
- rem/agentic/providers/pydantic_ai.py +572 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +396 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +231 -0
- rem/api/README.md +420 -0
- rem/api/main.py +324 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +536 -0
- rem/api/mcp_router/server.py +213 -0
- rem/api/mcp_router/tools.py +584 -0
- rem/api/routers/auth.py +229 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/completions.py +281 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +124 -0
- rem/api/routers/chat/streaming.py +185 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +26 -0
- rem/auth/middleware.py +100 -0
- rem/auth/providers/__init__.py +13 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +455 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +126 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +566 -0
- rem/cli/commands/configure.py +497 -0
- rem/cli/commands/db.py +493 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1302 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +245 -0
- rem/cli/commands/schema.py +183 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +96 -0
- rem/config.py +237 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +64 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +628 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +243 -0
- rem/models/entities/__init__.py +43 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +35 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +191 -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/user.py +85 -0
- rem/py.typed +0 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -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 +128 -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 +16 -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 +806 -0
- rem/services/content/service.py +676 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +336 -0
- rem/services/dreaming/moment_service.py +264 -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/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +120 -0
- rem/services/embeddings/worker.py +421 -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 +686 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +575 -0
- rem/services/postgres/__init__.py +23 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
- rem/services/postgres/register_type.py +352 -0
- rem/services/postgres/repository.py +337 -0
- rem/services/postgres/schema_generator.py +379 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +354 -0
- rem/services/rem/README.md +304 -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 +145 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +527 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +6 -0
- rem/services/session/compression.py +360 -0
- rem/services/session/reload.py +77 -0
- rem/settings.py +1235 -0
- rem/sql/002_install_models.sql +1068 -0
- rem/sql/background_indexes.sql +42 -0
- rem/sql/install_models.sql +1038 -0
- rem/sql/migrations/001_install.sql +503 -0
- rem/sql/migrations/002_install_models.sql +1202 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +583 -0
- rem/utils/__init__.py +43 -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/dict_utils.py +98 -0
- rem/utils/embeddings.py +423 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/markdown.py +16 -0
- rem/utils/model_helpers.py +236 -0
- rem/utils/schema_loader.py +336 -0
- rem/utils/sql_types.py +348 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +330 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +5 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- remdb-0.3.0.dist-info/METADATA +1455 -0
- remdb-0.3.0.dist-info/RECORD +187 -0
- remdb-0.3.0.dist-info/WHEEL +4 -0
- remdb-0.3.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Schema generation utility from Pydantic models.
|
|
3
|
+
|
|
4
|
+
Scans a directory of Pydantic models and generates complete database schemas including:
|
|
5
|
+
- Primary tables
|
|
6
|
+
- Embeddings tables
|
|
7
|
+
- KV_STORE triggers
|
|
8
|
+
- Indexes (foreground and background)
|
|
9
|
+
- Migrations
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from rem.services.postgres.schema_generator import SchemaGenerator
|
|
13
|
+
|
|
14
|
+
generator = SchemaGenerator()
|
|
15
|
+
schema = generator.generate_from_directory("src/rem/models/entities")
|
|
16
|
+
|
|
17
|
+
# Write to file
|
|
18
|
+
with open("src/rem/sql/schema.sql", "w") as f:
|
|
19
|
+
f.write(schema)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import importlib.util
|
|
23
|
+
import inspect
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Type
|
|
26
|
+
|
|
27
|
+
from loguru import logger
|
|
28
|
+
from pydantic import BaseModel
|
|
29
|
+
|
|
30
|
+
from ...settings import settings
|
|
31
|
+
from .register_type import register_type
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SchemaGenerator:
|
|
35
|
+
"""
|
|
36
|
+
Generate database schema from Pydantic models in a directory.
|
|
37
|
+
|
|
38
|
+
Discovers all Pydantic models in Python files and generates:
|
|
39
|
+
- CREATE TABLE statements
|
|
40
|
+
- Embeddings tables
|
|
41
|
+
- KV_STORE triggers
|
|
42
|
+
- Indexes
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, output_dir: Path | None = None):
|
|
46
|
+
"""
|
|
47
|
+
Initialize schema generator.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
output_dir: Optional directory for output files (defaults to settings.sql_dir)
|
|
51
|
+
"""
|
|
52
|
+
self.output_dir = output_dir or Path(settings.sql_dir)
|
|
53
|
+
self.schemas: dict[str, dict] = {}
|
|
54
|
+
|
|
55
|
+
def discover_models(self, directory: str | Path) -> dict[str, Type[BaseModel]]:
|
|
56
|
+
"""
|
|
57
|
+
Discover all Pydantic models in a directory.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
directory: Path to directory containing Python files with models
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dict mapping model name to model class
|
|
64
|
+
"""
|
|
65
|
+
import sys
|
|
66
|
+
import importlib
|
|
67
|
+
|
|
68
|
+
directory = Path(directory).resolve()
|
|
69
|
+
models = {}
|
|
70
|
+
|
|
71
|
+
logger.info(f"Discovering models in {directory}")
|
|
72
|
+
|
|
73
|
+
# Add src directory to Python path to handle relative imports
|
|
74
|
+
src_dir = directory
|
|
75
|
+
while src_dir.name != "src" and src_dir.parent != src_dir:
|
|
76
|
+
src_dir = src_dir.parent
|
|
77
|
+
|
|
78
|
+
if src_dir.name == "src" and str(src_dir) not in sys.path:
|
|
79
|
+
sys.path.insert(0, str(src_dir))
|
|
80
|
+
logger.debug(f"Added {src_dir} to sys.path for relative imports")
|
|
81
|
+
|
|
82
|
+
# Convert directory path to module path
|
|
83
|
+
# e.g., /path/to/src/rem/models/entities -> rem.models.entities
|
|
84
|
+
try:
|
|
85
|
+
rel_path = directory.relative_to(src_dir)
|
|
86
|
+
module_path = str(rel_path).replace("/", ".")
|
|
87
|
+
|
|
88
|
+
# Import the package to get all submodules
|
|
89
|
+
package = importlib.import_module(module_path)
|
|
90
|
+
|
|
91
|
+
# Find all Python files in the directory
|
|
92
|
+
for py_file in directory.rglob("*.py"):
|
|
93
|
+
if py_file.name.startswith("_"):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Build module name from file path
|
|
98
|
+
rel_file = py_file.relative_to(src_dir)
|
|
99
|
+
module_name = str(rel_file.with_suffix("")).replace("/", ".")
|
|
100
|
+
|
|
101
|
+
# Import the module
|
|
102
|
+
module = importlib.import_module(module_name)
|
|
103
|
+
|
|
104
|
+
# Find Pydantic models
|
|
105
|
+
for name, obj in inspect.getmembers(module):
|
|
106
|
+
if (
|
|
107
|
+
inspect.isclass(obj)
|
|
108
|
+
and issubclass(obj, BaseModel)
|
|
109
|
+
and obj is not BaseModel
|
|
110
|
+
and not name.startswith("_")
|
|
111
|
+
# Only include models defined in this module
|
|
112
|
+
and obj.__module__ == module_name
|
|
113
|
+
):
|
|
114
|
+
models[name] = obj
|
|
115
|
+
logger.debug(f"Found model: {name} in {module_name}")
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.warning(f"Failed to load {py_file}: {e}")
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"Failed to discover models in {directory}: {e}")
|
|
122
|
+
|
|
123
|
+
logger.info(f"Discovered {len(models)} models")
|
|
124
|
+
return models
|
|
125
|
+
|
|
126
|
+
def infer_table_name(self, model: Type[BaseModel]) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Infer table name from model class name.
|
|
129
|
+
|
|
130
|
+
Converts CamelCase to snake_case and pluralizes.
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
Resource -> resources
|
|
134
|
+
UserProfile -> user_profiles
|
|
135
|
+
Message -> messages
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
model: Pydantic model class
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Table name
|
|
142
|
+
"""
|
|
143
|
+
import re
|
|
144
|
+
|
|
145
|
+
name = model.__name__
|
|
146
|
+
|
|
147
|
+
# Convert CamelCase to snake_case
|
|
148
|
+
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
149
|
+
name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
|
|
150
|
+
|
|
151
|
+
# Simple pluralization (add 's' if doesn't end in 's')
|
|
152
|
+
if not name.endswith("s"):
|
|
153
|
+
if name.endswith("y"):
|
|
154
|
+
name = name[:-1] + "ies" # category -> categories
|
|
155
|
+
else:
|
|
156
|
+
name = name + "s" # resource -> resources
|
|
157
|
+
|
|
158
|
+
return name
|
|
159
|
+
|
|
160
|
+
def infer_entity_key_field(self, model: Type[BaseModel]) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Infer which field to use as entity_key in KV_STORE.
|
|
163
|
+
|
|
164
|
+
Priority:
|
|
165
|
+
1. Field with json_schema_extra={\"entity_key\": True}
|
|
166
|
+
2. Field named \"name\"
|
|
167
|
+
3. Field named \"key\"
|
|
168
|
+
4. Field named \"label\"
|
|
169
|
+
5. First string field
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
model: Pydantic model class
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Field name to use as entity_key
|
|
176
|
+
"""
|
|
177
|
+
# Check for explicit entity_key marker
|
|
178
|
+
for field_name, field_info in model.model_fields.items():
|
|
179
|
+
json_extra = getattr(field_info, "json_schema_extra", None)
|
|
180
|
+
if json_extra and isinstance(json_extra, dict):
|
|
181
|
+
if json_extra.get("entity_key"):
|
|
182
|
+
return field_name
|
|
183
|
+
|
|
184
|
+
# Check for key fields in priority order: id -> uri -> key -> name
|
|
185
|
+
# (matching sql_builder.get_entity_key convention)
|
|
186
|
+
for candidate in ["id", "uri", "key", "name"]:
|
|
187
|
+
if candidate in model.model_fields:
|
|
188
|
+
return candidate
|
|
189
|
+
|
|
190
|
+
# Should never reach here for CoreModel subclasses (they all have id)
|
|
191
|
+
logger.error(f"No suitable entity_key field found for {model.__name__}, using 'id'")
|
|
192
|
+
return "id"
|
|
193
|
+
|
|
194
|
+
async def generate_schema_for_model(
|
|
195
|
+
self,
|
|
196
|
+
model: Type[BaseModel],
|
|
197
|
+
table_name: str | None = None,
|
|
198
|
+
entity_key_field: str | None = None,
|
|
199
|
+
) -> dict:
|
|
200
|
+
"""
|
|
201
|
+
Generate schema for a single model.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
model: Pydantic model class
|
|
205
|
+
table_name: Optional table name (inferred if not provided)
|
|
206
|
+
entity_key_field: Optional entity key field (inferred if not provided)
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Dict with SQL statements and metadata
|
|
210
|
+
"""
|
|
211
|
+
if table_name is None:
|
|
212
|
+
table_name = self.infer_table_name(model)
|
|
213
|
+
|
|
214
|
+
if entity_key_field is None:
|
|
215
|
+
entity_key_field = self.infer_entity_key_field(model)
|
|
216
|
+
|
|
217
|
+
logger.info(f"Generating schema for {model.__name__} -> {table_name}")
|
|
218
|
+
|
|
219
|
+
schema = await register_type(
|
|
220
|
+
model=model,
|
|
221
|
+
table_name=table_name,
|
|
222
|
+
entity_key_field=entity_key_field,
|
|
223
|
+
tenant_scoped=True,
|
|
224
|
+
create_embeddings=True,
|
|
225
|
+
create_kv_trigger=True,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
self.schemas[table_name] = schema
|
|
229
|
+
return schema
|
|
230
|
+
|
|
231
|
+
async def generate_from_directory(
|
|
232
|
+
self, directory: str | Path, output_file: str | None = None
|
|
233
|
+
) -> str:
|
|
234
|
+
"""
|
|
235
|
+
Generate complete schema from all models in a directory.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
directory: Path to directory with Pydantic models
|
|
239
|
+
output_file: Optional output file path (relative to output_dir)
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Complete SQL schema as string
|
|
243
|
+
"""
|
|
244
|
+
# Discover models
|
|
245
|
+
models = self.discover_models(directory)
|
|
246
|
+
|
|
247
|
+
# Generate schemas for each model
|
|
248
|
+
for model_name, model in models.items():
|
|
249
|
+
await self.generate_schema_for_model(model)
|
|
250
|
+
|
|
251
|
+
# Combine into single SQL file
|
|
252
|
+
sql_parts = [
|
|
253
|
+
"-- REM Model Schema (install_models.sql)",
|
|
254
|
+
"-- Generated from Pydantic models",
|
|
255
|
+
f"-- Source directory: {directory}",
|
|
256
|
+
"-- Generated at: " + __import__("datetime").datetime.now().isoformat(),
|
|
257
|
+
"--",
|
|
258
|
+
"-- DO NOT EDIT MANUALLY - Regenerate with: rem db schema generate",
|
|
259
|
+
"--",
|
|
260
|
+
"-- This script creates:",
|
|
261
|
+
"-- 1. Primary entity tables",
|
|
262
|
+
"-- 2. Embeddings tables (embeddings_<table>)",
|
|
263
|
+
"-- 3. KV_STORE triggers for cache maintenance",
|
|
264
|
+
"-- 4. Indexes (foreground only, background indexes separate)",
|
|
265
|
+
"",
|
|
266
|
+
"-- ============================================================================",
|
|
267
|
+
"-- PREREQUISITES CHECK",
|
|
268
|
+
"-- ============================================================================",
|
|
269
|
+
"",
|
|
270
|
+
"DO $$",
|
|
271
|
+
"BEGIN",
|
|
272
|
+
" -- Check that install.sql has been run",
|
|
273
|
+
" IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'kv_store') THEN",
|
|
274
|
+
" RAISE EXCEPTION 'KV_STORE table not found. Run migrations/001_install.sql first.';",
|
|
275
|
+
" END IF;",
|
|
276
|
+
"",
|
|
277
|
+
" IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN",
|
|
278
|
+
" RAISE EXCEPTION 'pgvector extension not found. Run migrations/001_install.sql first.';",
|
|
279
|
+
" END IF;",
|
|
280
|
+
"",
|
|
281
|
+
" RAISE NOTICE 'Prerequisites check passed';",
|
|
282
|
+
"END $$;",
|
|
283
|
+
"",
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
# Add each table schema
|
|
287
|
+
for table_name, schema in self.schemas.items():
|
|
288
|
+
sql_parts.append("-- " + "=" * 70)
|
|
289
|
+
sql_parts.append(f"-- {table_name.upper()} (Model: {schema['model']})")
|
|
290
|
+
sql_parts.append("-- " + "=" * 70)
|
|
291
|
+
sql_parts.append("")
|
|
292
|
+
|
|
293
|
+
# Primary table
|
|
294
|
+
if "table" in schema["sql"]:
|
|
295
|
+
sql_parts.append(schema["sql"]["table"])
|
|
296
|
+
sql_parts.append("")
|
|
297
|
+
|
|
298
|
+
# Embeddings table
|
|
299
|
+
if "embeddings" in schema["sql"] and schema["sql"]["embeddings"]:
|
|
300
|
+
sql_parts.append(f"-- Embeddings for {table_name}")
|
|
301
|
+
sql_parts.append(schema["sql"]["embeddings"])
|
|
302
|
+
sql_parts.append("")
|
|
303
|
+
|
|
304
|
+
# KV_STORE trigger
|
|
305
|
+
if "kv_trigger" in schema["sql"]:
|
|
306
|
+
sql_parts.append(f"-- KV_STORE trigger for {table_name}")
|
|
307
|
+
sql_parts.append(schema["sql"]["kv_trigger"])
|
|
308
|
+
sql_parts.append("")
|
|
309
|
+
|
|
310
|
+
# Add migration record
|
|
311
|
+
sql_parts.append("-- ============================================================================")
|
|
312
|
+
sql_parts.append("-- RECORD MIGRATION")
|
|
313
|
+
sql_parts.append("-- ============================================================================")
|
|
314
|
+
sql_parts.append("")
|
|
315
|
+
sql_parts.append("INSERT INTO rem_migrations (name, type, version)")
|
|
316
|
+
sql_parts.append("VALUES ('install_models.sql', 'models', '1.0.0')")
|
|
317
|
+
sql_parts.append("ON CONFLICT (name) DO UPDATE")
|
|
318
|
+
sql_parts.append("SET applied_at = CURRENT_TIMESTAMP,")
|
|
319
|
+
sql_parts.append(" applied_by = CURRENT_USER;")
|
|
320
|
+
sql_parts.append("")
|
|
321
|
+
|
|
322
|
+
# Completion message
|
|
323
|
+
sql_parts.append("DO $$")
|
|
324
|
+
sql_parts.append("BEGIN")
|
|
325
|
+
sql_parts.append(" RAISE NOTICE '============================================================';")
|
|
326
|
+
sql_parts.append(f" RAISE NOTICE 'REM Model Schema Applied: {len(self.schemas)} tables';")
|
|
327
|
+
sql_parts.append(" RAISE NOTICE '============================================================';")
|
|
328
|
+
for table_name in sorted(self.schemas.keys()):
|
|
329
|
+
embeddable = len(self.schemas[table_name].get("embeddable_fields", []))
|
|
330
|
+
embed_info = f" ({embeddable} embeddable fields)" if embeddable else ""
|
|
331
|
+
sql_parts.append(f" RAISE NOTICE ' ✓ {table_name}{embed_info}';")
|
|
332
|
+
sql_parts.append(" RAISE NOTICE '';")
|
|
333
|
+
sql_parts.append(" RAISE NOTICE 'Next: Run background indexes if needed';")
|
|
334
|
+
sql_parts.append(" RAISE NOTICE ' rem db migrate --background-indexes';")
|
|
335
|
+
sql_parts.append(" RAISE NOTICE '============================================================';")
|
|
336
|
+
sql_parts.append("END $$;")
|
|
337
|
+
|
|
338
|
+
complete_sql = "\n".join(sql_parts)
|
|
339
|
+
|
|
340
|
+
# Write to file if specified
|
|
341
|
+
if output_file:
|
|
342
|
+
output_path = self.output_dir / output_file
|
|
343
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
344
|
+
output_path.write_text(complete_sql)
|
|
345
|
+
logger.info(f"Schema written to {output_path}")
|
|
346
|
+
|
|
347
|
+
return complete_sql
|
|
348
|
+
|
|
349
|
+
def generate_background_indexes(self) -> str:
|
|
350
|
+
"""
|
|
351
|
+
Generate SQL for background index creation.
|
|
352
|
+
|
|
353
|
+
These indexes are created CONCURRENTLY to avoid blocking writes.
|
|
354
|
+
Should be run after initial data load.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
SQL for background index creation
|
|
358
|
+
"""
|
|
359
|
+
sql_parts = [
|
|
360
|
+
"-- Background index creation",
|
|
361
|
+
"-- Run AFTER initial data load to avoid blocking writes",
|
|
362
|
+
"",
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
for table_name, schema in self.schemas.items():
|
|
366
|
+
if not schema.get("embeddable_fields"):
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
embeddings_table = f"embeddings_{table_name}"
|
|
370
|
+
|
|
371
|
+
sql_parts.append(f"-- HNSW vector index for {embeddings_table}")
|
|
372
|
+
sql_parts.append(
|
|
373
|
+
f"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_{embeddings_table}_vector_hnsw"
|
|
374
|
+
)
|
|
375
|
+
sql_parts.append(f"ON {embeddings_table}")
|
|
376
|
+
sql_parts.append("USING hnsw (embedding vector_cosine_ops);")
|
|
377
|
+
sql_parts.append("")
|
|
378
|
+
|
|
379
|
+
return "\n".join(sql_parts)
|