Skip to content

Commit 3e6cbb2

Browse files
committed
feat: optimize SQLite storage for large codebases with FTS5 and PRAGMA tuning
1 parent 4094822 commit 3e6cbb2

1 file changed

Lines changed: 37 additions & 9 deletions

File tree

codeindex-core/src/main/java/io/appform/codeindex/storage/SQLiteStorage.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public SQLiteStorage(String dbPath) throws SQLException {
3939
SQLiteStorage(Connection connection) throws SQLException {
4040
this.connection = connection;
4141
try {
42+
tuneDatabase();
4243
initializeSchema();
4344
} catch (SQLException e) {
4445
try {
@@ -50,6 +51,16 @@ public SQLiteStorage(String dbPath) throws SQLException {
5051
}
5152
}
5253

54+
private void tuneDatabase() throws SQLException {
55+
try (Statement stmt = connection.createStatement()) {
56+
stmt.execute("PRAGMA journal_mode = WAL");
57+
stmt.execute("PRAGMA synchronous = NORMAL");
58+
stmt.execute("PRAGMA cache_size = -20000"); // 20MB
59+
stmt.execute("PRAGMA temp_store = MEMORY");
60+
stmt.execute("PRAGMA mmap_size = 30000000000"); // Up to 30GB mmap
61+
}
62+
}
63+
5364
private void initializeSchema() throws SQLException {
5465
try (Statement stmt = connection.createStatement()) {
5566
stmt.execute("""
@@ -65,6 +76,19 @@ CREATE TABLE IF NOT EXISTS symbols (
6576
reference_to TEXT
6677
)
6778
""");
79+
stmt.execute("""
80+
CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
81+
name, class_name, package_name,
82+
content='symbols',
83+
content_rowid='id'
84+
)
85+
""");
86+
stmt.execute("""
87+
CREATE TRIGGER IF NOT EXISTS symbols_ai AFTER INSERT ON symbols BEGIN
88+
INSERT INTO symbols_fts(rowid, name, class_name, package_name)
89+
VALUES (new.id, new.name, new.class_name, new.package_name);
90+
END
91+
""");
6892
stmt.execute("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)");
6993
stmt.execute("CREATE INDEX IF NOT EXISTS idx_symbols_reference_to ON symbols(reference_to)");
7094
stmt.execute("CREATE INDEX IF NOT EXISTS idx_symbols_class_name ON symbols(class_name)");
@@ -74,8 +98,10 @@ CREATE TABLE IF NOT EXISTS symbols (
7498

7599
public void saveSymbols(List<Symbol> symbols) throws SQLException {
76100
final var sql = "INSERT INTO symbols (name, class_name, package_name, kind, file_path, line, signature, reference_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
101+
final var batchSize = 1000;
77102
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
78103
connection.setAutoCommit(false);
104+
int count = 0;
79105
for (Symbol symbol : symbols) {
80106
pstmt.setString(1, symbol.getName());
81107
pstmt.setString(2, symbol.getClassName());
@@ -86,6 +112,10 @@ public void saveSymbols(List<Symbol> symbols) throws SQLException {
86112
pstmt.setString(7, symbol.getSignature());
87113
pstmt.setString(8, symbol.getReferenceTo());
88114
pstmt.addBatch();
115+
116+
if (++count % batchSize == 0) {
117+
pstmt.executeBatch();
118+
}
89119
}
90120
pstmt.executeBatch();
91121
connection.commit();
@@ -117,24 +147,22 @@ public List<Symbol> search(String query, int limit) throws SQLException {
117147
}
118148

119149
public List<Symbol> search(SearchRequest request) throws SQLException {
120-
final var sql = new StringBuilder("SELECT name, class_name, package_name, kind, file_path, line, signature, reference_to FROM symbols WHERE 1=1");
150+
final var sql = new StringBuilder("SELECT name, class_name, package_name, kind, file_path, line, signature, reference_to FROM symbols WHERE id IN (SELECT rowid FROM symbols_fts WHERE symbols_fts MATCH ?)");
121151
final var params = new ArrayList<>();
122152

123153
if (request.getQuery() != null && !request.getQuery().isBlank()) {
124154
if (request.getQuery().contains("::")) {
125155
final var parts = request.getQuery().split("::");
126156
final var containerTerm = parts[0];
127157
final var symbolTerm = parts[1];
128-
sql.append(" AND name LIKE ? AND (class_name LIKE ? OR package_name LIKE ?)");
129-
params.add("%" + symbolTerm + "%");
130-
params.add("%" + containerTerm + "%");
131-
params.add("%" + containerTerm + "%");
158+
params.add(String.format("name:%s* AND (class_name:%s* OR package_name:%s*)", symbolTerm, containerTerm, containerTerm));
132159
} else {
133-
sql.append(" AND (name LIKE ? OR class_name LIKE ? OR package_name LIKE ?)");
134-
params.add("%" + request.getQuery() + "%");
135-
params.add("%" + request.getQuery() + "%");
136-
params.add("%" + request.getQuery() + "%");
160+
params.add(String.format("name:%s* OR class_name:%s* OR package_name:%s*", request.getQuery(), request.getQuery(), request.getQuery()));
137161
}
162+
} else {
163+
// Fallback for empty query if other filters are present
164+
sql.setLength(0);
165+
sql.append("SELECT name, class_name, package_name, kind, file_path, line, signature, reference_to FROM symbols WHERE 1=1");
138166
}
139167

140168
if (request.getClassName() != null && !request.getClassName().isBlank()) {

0 commit comments

Comments
 (0)