Skip to content

Commit 6d88ed5

Browse files
authored
feat: Enable FTS5 in sql.js build and add test page (#1)
1 parent 6674a8f commit 6d88ed5

File tree

3 files changed

+429
-1
lines changed

3 files changed

+429
-1
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ SQLITE_COMPILATION_FLAGS = \
2424
-DSQLITE_ENABLE_FTS3 \
2525
-DSQLITE_ENABLE_FTS3_PARENTHESIS \
2626
-DSQLITE_THREADSAFE=0 \
27-
-DSQLITE_ENABLE_NORMALIZE
27+
-DSQLITE_ENABLE_NORMALIZE \
28+
-DSQLITE_ENABLE_FTS5
2829

2930
# When compiling to WASM, enabling memory-growth is not expected to make much of an impact, so we enable it for all builds
3031
# Since tihs is a library and not a standalone executable, we don't want to catch unhandled Node process exceptions

test-search-live.html

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>SQL.js Live Search with FTS5</title>
5+
<style>
6+
body {
7+
font-family: Arial, sans-serif;
8+
max-width: 800px;
9+
margin: 0 auto;
10+
padding: 20px;
11+
}
12+
.download-btn {
13+
margin: 20px 0;
14+
padding: 10px 20px;
15+
font-size: 16px;
16+
background-color: #4CAF50;
17+
color: white;
18+
border: none;
19+
border-radius: 4px;
20+
cursor: pointer;
21+
}
22+
.download-btn:hover {
23+
background-color: #45a049;
24+
}
25+
.search-container {
26+
margin: 20px 0;
27+
}
28+
#searchInput {
29+
padding: 10px;
30+
width: 70%;
31+
font-size: 16px;
32+
border: 1px solid #ddd;
33+
border-radius: 4px;
34+
}
35+
#searchButton {
36+
padding: 10px 20px;
37+
font-size: 16px;
38+
background-color: #2196F3;
39+
color: white;
40+
border: none;
41+
border-radius: 4px;
42+
cursor: pointer;
43+
}
44+
#searchButton:hover {
45+
background-color: #0b7dda;
46+
}
47+
.result-item {
48+
margin: 10px 0;
49+
padding: 15px;
50+
border: 1px solid #ddd;
51+
border-radius: 4px;
52+
background-color: #f9f9f9;
53+
}
54+
.result-title {
55+
font-weight: bold;
56+
font-size: 18px;
57+
margin-bottom: 5px;
58+
}
59+
.result-body {
60+
color: #555;
61+
}
62+
.highlight {
63+
background-color: yellow;
64+
font-weight: bold;
65+
}
66+
#queryTime {
67+
margin: 10px 0;
68+
padding: 5px 10px;
69+
font-size: 14px;
70+
background-color: #f0f0f0;
71+
border-left: 3px solid #2196F3;
72+
color: #666;
73+
}
74+
</style>
75+
</head>
76+
<body>
77+
<h1>SQL.js Live Search with FTS5</h1>
78+
79+
<div class="search-container">
80+
<input type="text" id="searchInput" placeholder="Enter search term and click Search">
81+
<button id="searchButton">Search</button>
82+
</div>
83+
84+
<div id="status"></div>
85+
<div id="queryTime"></div>
86+
<div id="searchResults"></div>
87+
<button id="downloadDb" class="download-btn" style="display: none;">Download Database</button>
88+
89+
<script>
90+
// Global database variable
91+
let db;
92+
93+
// Debounce function to limit how often the search is triggered while typing
94+
function debounce(func, wait) {
95+
let timeout;
96+
return function(...args) {
97+
const context = this;
98+
clearTimeout(timeout);
99+
timeout = setTimeout(() => func.apply(context, args), wait);
100+
};
101+
}
102+
103+
// Helper function to display status
104+
function displayStatus(text) {
105+
const status = document.getElementById('status');
106+
status.innerHTML = `<p>${text}</p>`;
107+
}
108+
109+
// Helper function to download the database
110+
function downloadDatabase() {
111+
if (!db) return;
112+
113+
const data = db.export();
114+
const blob = new Blob([data], { type: 'application/x-sqlite3' });
115+
const url = URL.createObjectURL(blob);
116+
const a = document.createElement('a');
117+
a.href = url;
118+
a.download = 'search-database.sqlite';
119+
a.click();
120+
URL.revokeObjectURL(url);
121+
}
122+
123+
// Function to display search results
124+
function displayResults(results) {
125+
const searchResults = document.getElementById('searchResults');
126+
searchResults.innerHTML = '';
127+
128+
if (!results || results.length === 0 || !results[0].values || results[0].values.length === 0) {
129+
searchResults.innerHTML = '<p>No results found.</p>';
130+
return;
131+
}
132+
133+
const searchTerm = document.getElementById('searchInput').value.trim();
134+
135+
// Extract column names from the results
136+
const columns = results[0].columns;
137+
const titleIndex = columns.indexOf('title');
138+
const bodyIndex = columns.indexOf('body');
139+
140+
// Create result items
141+
results[0].values.forEach(row => {
142+
const title = row[titleIndex];
143+
const body = row[bodyIndex];
144+
145+
const resultItem = document.createElement('div');
146+
resultItem.className = 'result-item';
147+
148+
// Highlight matching prefixes in title and body if present
149+
let highlightedTitle = title;
150+
let highlightedBody = body;
151+
152+
if (searchTerm) {
153+
// Create a regex that matches the prefix pattern
154+
const prefixRegex = new RegExp(`\\b${searchTerm}\\w*`, 'gi');
155+
highlightedTitle = title.replace(prefixRegex, match => `<span class="highlight">${match}</span>`);
156+
highlightedBody = body.replace(prefixRegex, match => `<span class="highlight">${match}</span>`);
157+
}
158+
159+
resultItem.innerHTML = `
160+
<div class="result-title">${highlightedTitle}</div>
161+
<div class="result-body">${highlightedBody}</div>
162+
`;
163+
164+
searchResults.appendChild(resultItem);
165+
});
166+
}
167+
168+
// Function to perform search
169+
function performSearch() {
170+
if (!db) {
171+
displayStatus('Database not ready yet. Please wait...');
172+
return;
173+
}
174+
175+
const searchTerm = document.getElementById('searchInput').value.trim();
176+
const queryTimeElement = document.getElementById('queryTime');
177+
queryTimeElement.innerHTML = '';
178+
179+
if (!searchTerm) {
180+
// If search is empty, show all articles
181+
try {
182+
const startTime = performance.now();
183+
const allResults = db.exec('SELECT rowid, title, body FROM articles ORDER BY title');
184+
const endTime = performance.now();
185+
const queryTime = endTime - startTime;
186+
187+
queryTimeElement.innerHTML = `<p>Query execution time: ${queryTime.toFixed(2)} ms</p>`;
188+
displayResults(allResults);
189+
} catch (err) {
190+
displayStatus('Error retrieving articles: ' + err.message);
191+
console.error(err);
192+
}
193+
return;
194+
}
195+
196+
try {
197+
// Perform FTS5 search with prefix matching using the * wildcard
198+
const prefixSearchTerm = `${searchTerm}*`;
199+
200+
const startTime = performance.now();
201+
const results = db.exec(`
202+
SELECT rowid, title, body FROM articles
203+
WHERE articles MATCH ?
204+
ORDER BY rank
205+
`, [prefixSearchTerm]);
206+
const endTime = performance.now();
207+
const queryTime = endTime - startTime;
208+
209+
queryTimeElement.innerHTML = `<p>Query execution time: ${queryTime.toFixed(2)} ms</p>`;
210+
displayResults(results);
211+
} catch (err) {
212+
displayStatus('Search error: ' + err.message);
213+
console.error(err);
214+
}
215+
}
216+
217+
// Initialize database and populate with example data
218+
async function initDatabase() {
219+
try {
220+
displayStatus('Loading SQL.js...');
221+
222+
// Initialize SQL.js with the correct path to wasm file
223+
const SQL = await initSqlJs({
224+
locateFile: file => `./dist/${file}`
225+
});
226+
displayStatus('SQL.js loaded successfully!');
227+
228+
// Create a database
229+
db = new SQL.Database();
230+
displayStatus('Created an in-memory database');
231+
232+
// Create FTS5 virtual table
233+
db.run(`
234+
CREATE VIRTUAL TABLE articles USING fts5(
235+
title,
236+
body
237+
);
238+
`);
239+
displayStatus('Created FTS5 virtual table');
240+
241+
// Insert example data
242+
db.run(`
243+
INSERT INTO articles (title, body) VALUES
244+
('Introduction to SQL', 'SQL (Structured Query Language) is a standard language for storing, manipulating, and retrieving data in databases. SQL statements are used to perform tasks such as update data or retrieve data from a database.'),
245+
('JavaScript Fundamentals', 'JavaScript is a programming language commonly used for web development. It allows you to implement complex features on web pages like interactive maps, animated graphics, and more.'),
246+
('Python for Beginners', 'Python is a popular programming language. It was created by Guido van Rossum and released in 1991. It is used for web development, software development, mathematics, system scripting, and more.'),
247+
('Web Development Basics', 'Web development refers to building, creating, and maintaining websites. It includes aspects such as web design, web publishing, web programming, and database management.'),
248+
('Database Design Principles', 'Good database design is crucial for building scalable and efficient applications. It involves normalization, indexing strategies, and choosing the right data types.'),
249+
('Machine Learning Introduction', 'Machine learning is a method of data analysis that automates analytical model building. It is a branch of artificial intelligence based on the idea that systems can learn from data.'),
250+
('HTML and CSS Basics', 'HTML (Hypertext Markup Language) is the standard markup language for documents designed to be displayed in a web browser. CSS (Cascading Style Sheets) is used to style and layout web pages.'),
251+
('Mobile App Development', 'Mobile app development involves creating applications that run on mobile devices. It requires considering screen sizes, hardware specifications, and various configurations.'),
252+
('Cloud Computing Services', 'Cloud computing is the delivery of computing services over the internet. Services include servers, storage, databases, networking, software, and analytics.'),
253+
('Cybersecurity Best Practices', 'Cybersecurity involves protecting systems, networks, and programs from digital attacks. These attacks often aim to access, change, or destroy sensitive information.')
254+
`);
255+
displayStatus('Database populated with example data');
256+
257+
// Show all articles initially
258+
const initialResults = db.exec('SELECT rowid, title, body FROM articles ORDER BY title');
259+
displayResults(initialResults);
260+
261+
// Show download button
262+
const downloadBtn = document.getElementById('downloadDb');
263+
downloadBtn.style.display = 'block';
264+
downloadBtn.onclick = downloadDatabase;
265+
266+
// Setup search button (kept for accessibility)
267+
document.getElementById('searchButton').addEventListener('click', performSearch);
268+
269+
// Setup enter key in search input
270+
document.getElementById('searchInput').addEventListener('keyup', function(event) {
271+
if (event.key === 'Enter') {
272+
performSearch();
273+
}
274+
});
275+
276+
displayStatus('Ready to search! Enter a term and click Search.');
277+
} catch (err) {
278+
displayStatus('Error: ' + err.message);
279+
console.error(err);
280+
}
281+
}
282+
</script>
283+
284+
<!-- Load sql.js -->
285+
<script src="./dist/sql-wasm.js"></script>
286+
<script>
287+
// Start initialization after page loads
288+
window.addEventListener('load', initDatabase);
289+
</script>
290+
</body>
291+
</html>

0 commit comments

Comments
 (0)