Repository navigation
Expand file tree
/
Copy pathsetup.sql
More file actions
352 lines (321 loc) · 12.5 KB
/
Copy pathsetup.sql
File metadata and controls
352 lines (321 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
-- AI Tutor Backend - PDF Storage & Vector Database Setup
-- Supabase + pgvector configuration for LLM data retrieval
--
-- UPDATES INCLUDED:
-- - User isolation: Added user_id to pdf_documents table
-- - Fixed embedding dimensions: Updated all functions to use 768-dim vectors (nomic-embed-text)
-- - Corrected get_user_subjects function: Fixed ambiguous columns and return types
-- - Enhanced RLS policies: Added performance improvements and documentation
-- - Added user_id indexes: Optimized queries for multi-user performance
--
-- This schema supports:
-- ✅ Strict user isolation with Row-Level Security (RLS)
-- ✅ Document-specific question answering
-- ✅ 768-dimensional Ollama embeddings (nomic-embed-text)
-- ✅ Multi-user PDF processing and storage
-- ✅ Advanced vector similarity search
-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS vector;
-- User Management (Basic for PDF ownership)
CREATE TABLE users (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255),
role VARCHAR(50) DEFAULT 'student',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Subject Management (Categorize PDFs by subject)
CREATE TABLE subjects (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name VARCHAR(255) NOT NULL,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
description TEXT,
color VARCHAR(7) DEFAULT '#3B82F6', -- For UI display
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, name) -- Prevent duplicate subject names per user
);
-- PDF Document Storage
CREATE TABLE pdf_documents (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
filename VARCHAR(255) NOT NULL, -- Stored filename
original_filename VARCHAR(255) NOT NULL, -- User's original filename
subject_id UUID REFERENCES subjects(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE, -- User ownership for isolation
file_path TEXT, -- Path in Supabase storage
file_size BIGINT NOT NULL,
total_pages INTEGER,
upload_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
processed BOOLEAN DEFAULT FALSE,
processing_status VARCHAR(50) DEFAULT 'pending', -- 'pending', 'processing', 'completed', 'failed'
processing_error TEXT, -- Store error details if processing fails
metadata JSONB, -- Additional PDF metadata (author, creation date, etc.)
chunk_count INTEGER DEFAULT 0, -- Track how many chunks were created
total_chunks INTEGER DEFAULT 0 -- Alias for consistency
);
-- Document Chunks with Vector Embeddings (Core for LLM retrieval)
CREATE TABLE document_chunks (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
pdf_id UUID REFERENCES pdf_documents(id) ON DELETE CASCADE,
content TEXT NOT NULL, -- Actual text content
chunk_index INTEGER NOT NULL, -- Order within the document
page_number INTEGER, -- Which page this chunk came from
embedding VECTOR(768), -- Ollama nomic-embed-text model (768 dimensions)
token_count INTEGER, -- Number of tokens in this chunk
metadata JSONB, -- Additional context
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- Ensure unique ordering within each PDF
UNIQUE(pdf_id, chunk_index)
);
-- Chunk metadata structure:
COMMENT ON COLUMN document_chunks.metadata IS
'JSON structure: {
"subject": "Mathematics",
"topic": "Calculus",
"section": "Derivatives",
"difficulty_level": 2,
"chunk_type": "definition|example|explanation|formula|theorem",
"keywords": ["derivative", "limit", "function"],
"page_start": 15,
"page_end": 16
}';
-- PDF Processing Queue (Track processing status)
CREATE TABLE processing_queue (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
pdf_id UUID REFERENCES pdf_documents(id) ON DELETE CASCADE,
status VARCHAR(50) DEFAULT 'queued', -- 'queued', 'processing', 'completed', 'failed'
started_at TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
error_message TEXT,
retry_count INTEGER DEFAULT 0,
max_retries INTEGER DEFAULT 3,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create indexes for optimal performance
-- Vector similarity search index (most important for LLM retrieval)
CREATE INDEX idx_document_chunks_embedding
ON document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Indexes for fast PDF and subject lookups
CREATE INDEX idx_pdf_documents_subject_id ON pdf_documents(subject_id);
CREATE INDEX idx_pdf_documents_user_id ON pdf_documents(user_id); -- Added for user isolation performance
CREATE INDEX idx_pdf_documents_processed ON pdf_documents(processed, processing_status);
CREATE INDEX idx_document_chunks_pdf_id ON document_chunks(pdf_id);
CREATE INDEX idx_document_chunks_page ON document_chunks(page_number);
CREATE INDEX idx_subjects_user_id ON subjects(user_id);
CREATE INDEX idx_processing_queue_status ON processing_queue(status);
-- Full-text search index for content (backup search method)
CREATE INDEX idx_document_chunks_content_fts
ON document_chunks
USING gin(to_tsvector('english', content));
-- Vector search function for LLM retrieval
CREATE OR REPLACE FUNCTION match_documents_by_subject (
query_embedding VECTOR(768),
subject_name VARCHAR(255),
user_id UUID,
match_threshold FLOAT DEFAULT 0.75,
match_count INT DEFAULT 5
)
RETURNS TABLE (
chunk_id UUID,
content TEXT,
metadata JSONB,
similarity FLOAT,
pdf_title VARCHAR(255),
page_number INTEGER,
subject_name_result VARCHAR(255)
)
LANGUAGE SQL STABLE
AS $$
SELECT
dc.id as chunk_id,
dc.content,
dc.metadata,
1 - (dc.embedding <=> query_embedding) AS similarity,
pd.original_filename as pdf_title,
dc.page_number,
s.name as subject_name_result
FROM document_chunks dc
JOIN pdf_documents pd ON dc.pdf_id = pd.id
JOIN subjects s ON pd.subject_id = s.id
WHERE s.name ILIKE subject_name
AND s.user_id = match_documents_by_subject.user_id -- FIXED HERE
AND pd.processed = TRUE
AND dc.embedding IS NOT NULL
AND 1 - (dc.embedding <=> query_embedding) > match_threshold
ORDER BY dc.embedding <=> query_embedding
LIMIT match_count;
$$;
-- Function to get all available subjects for a user
CREATE OR REPLACE FUNCTION get_user_subjects(p_user_id UUID)
RETURNS TABLE (
id UUID,
name TEXT,
description TEXT,
color VARCHAR(7), -- Changed from TEXT to VARCHAR(7) to match database column type
pdf_count BIGINT,
created_at TIMESTAMPTZ
) AS $$
BEGIN
RETURN QUERY
SELECT
s.id,
s.name,
s.description,
s.color,
COUNT(p.id) as pdf_count,
s.created_at
FROM subjects s
LEFT JOIN pdf_documents p ON s.id = p.subject_id AND p.user_id = p_user_id
WHERE s.user_id = p_user_id
GROUP BY s.id, s.name, s.description, s.color, s.created_at
ORDER BY s.created_at DESC;
END;
$$ LANGUAGE plpgsql;
-- Function to search within a specific PDF
CREATE OR REPLACE FUNCTION search_within_pdf (
query_embedding VECTOR(768), -- Updated to 768 for nomic-embed-text
pdf_document_id UUID,
match_threshold FLOAT DEFAULT 0.75,
match_count INT DEFAULT 10
)
RETURNS TABLE (
chunk_id UUID,
content TEXT,
page_number INTEGER,
similarity FLOAT,
chunk_index INTEGER
)
LANGUAGE SQL STABLE
AS $$
SELECT
dc.id as chunk_id,
dc.content,
dc.page_number,
1 - (dc.embedding <=> query_embedding) AS similarity,
dc.chunk_index
FROM document_chunks dc
WHERE dc.pdf_id = pdf_document_id
AND dc.embedding IS NOT NULL
AND 1 - (dc.embedding <=> query_embedding) > match_threshold
ORDER BY dc.embedding <=> query_embedding
LIMIT match_count;
$$;
-- Function to get PDF processing statistics
CREATE OR REPLACE FUNCTION get_processing_stats()
RETURNS TABLE (
total_pdfs BIGINT,
processed_pdfs BIGINT,
processing_pdfs BIGINT,
failed_pdfs BIGINT,
total_chunks BIGINT,
avg_chunks_per_pdf NUMERIC
)
LANGUAGE SQL STABLE
AS $$
SELECT
COUNT(*) as total_pdfs,
COUNT(*) FILTER (WHERE processed = TRUE) as processed_pdfs,
COUNT(*) FILTER (WHERE processing_status = 'processing') as processing_pdfs,
COUNT(*) FILTER (WHERE processing_status = 'failed') as failed_pdfs,
COALESCE(SUM(chunk_count), 0) as total_chunks,
CASE
WHEN COUNT(*) FILTER (WHERE processed = TRUE) > 0
THEN ROUND(SUM(chunk_count)::NUMERIC / COUNT(*) FILTER (WHERE processed = TRUE), 2)
ELSE 0
END as avg_chunks_per_pdf
FROM pdf_documents;
$$;
-- Row Level Security (RLS) for data isolation
-- Note: RLS policies use auth.uid() which works with Supabase Auth
-- For custom authentication, the application bypasses RLS using service_client
-- after proper user verification at the application level
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE subjects ENABLE ROW LEVEL SECURITY;
ALTER TABLE pdf_documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;
ALTER TABLE processing_queue ENABLE ROW LEVEL SECURITY;
-- RLS Policies for Supabase Auth (when using built-in authentication)
-- For custom auth, these are bypassed using service_client with app-level verification
-- Users can only see their own data
CREATE POLICY "Users can view own profile" ON users
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON users
FOR UPDATE USING (auth.uid() = id);
-- Users can only access their own subjects
CREATE POLICY "Users can manage own subjects" ON subjects
FOR ALL USING (auth.uid() = user_id);
-- Users can only access PDFs in their subjects
-- Updated to include direct user_id check for better performance
CREATE POLICY "Users can access own PDFs" ON pdf_documents
FOR ALL USING (
auth.uid() = user_id OR
EXISTS (
SELECT 1 FROM subjects
WHERE subjects.id = pdf_documents.subject_id
AND subjects.user_id = auth.uid()
)
);
-- Users can only access chunks from their PDFs
CREATE POLICY "Users can access own document chunks" ON document_chunks
FOR ALL USING (
EXISTS (
SELECT 1 FROM pdf_documents pd
JOIN subjects s ON pd.subject_id = s.id
WHERE pd.id = document_chunks.pdf_id
AND s.user_id = auth.uid()
)
);
-- Processing queue access
CREATE POLICY "Users can view own processing jobs" ON processing_queue
FOR ALL USING (
EXISTS (
SELECT 1 FROM pdf_documents pd
JOIN subjects s ON pd.subject_id = s.id
WHERE pd.id = processing_queue.pdf_id
AND s.user_id = auth.uid()
)
);
-- Insert some sample data for testing (optional)
-- Uncomment below if you want test data
/*
-- Sample user
INSERT INTO users (id, name, email, role) VALUES
('550e8400-e29b-41d4-a716-446655440000', 'Test Student', 'student@test.com', 'student');
-- Sample subjects
INSERT INTO subjects (id, name, user_id, description) VALUES
('550e8400-e29b-41d4-a716-446655440001', 'Mathematics', '550e8400-e29b-41d4-a716-446655440000', 'Calculus and Algebra'),
('550e8400-e29b-41d4-a716-446655440002', 'Physics', '550e8400-e29b-41d4-a716-446655440000', 'Mechanics and Thermodynamics');
*/
-- View to easily see PDF processing status
CREATE VIEW pdf_processing_overview AS
SELECT
pd.id,
pd.original_filename,
s.name as subject_name,
u.name as user_name,
pd.file_size,
pd.total_pages,
pd.chunk_count,
pd.processing_status,
pd.upload_date,
CASE
WHEN pd.processed = TRUE THEN 'Ready for LLM'
WHEN pd.processing_status = 'processing' THEN 'Being Processed'
WHEN pd.processing_status = 'failed' THEN 'Failed - ' || COALESCE(pd.processing_error, 'Unknown error')
ELSE 'Waiting to Process'
END as status_description
FROM pdf_documents pd
JOIN subjects s ON pd.subject_id = s.id
JOIN users u ON s.user_id = u.id
ORDER BY pd.upload_date DESC;
-- Grant necessary permissions for the application to work
-- Note: In production, create specific service roles with minimal required permissions
COMMENT ON TABLE pdf_documents IS 'Stores metadata about uploaded PDF files';
COMMENT ON TABLE document_chunks IS 'Stores text chunks with vector embeddings for LLM retrieval';
COMMENT ON TABLE subjects IS 'Organizes PDFs by academic subjects';
COMMENT ON INDEX idx_document_chunks_embedding IS 'Vector similarity search index - critical for LLM performance';
-- Complete! Database is ready for PDF storage and LLM data retrieval