-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add logic to store data by native code (#130)
* refactor: make logic for Database be abstract * feat: add logic for DB by string.dump * fix: run with async.void to run synchronously * test: add tests for native feature * feat!: sort candidates by path when score is same This is needed because candidates from SQLite is sorted by id, but ones from native is sorted by path. * chore: clean up types * feat: add lock/unlock feature to access DB * test: use async version of busted And disable benchmark tests (fix later) * test: add tests for file_lock * chore: use more explicit names * chore: use plenary.log instead * fix: wait async functions definitely * feat: add migrator * chore: fix logging * fix: detect emptiness of the table * fix: deal with buffer with no names * test: loosen the condition temporarily * test: add tests for migrator * fix: return true when the table is not empty * feat: load sqlite lazily not to require in start * chore: add logging to calculate time for fetching * feat: add converter from native code to SQLite * feat: warn when sqlite.lua is not available * feat: add FrecencyMigrateDB to migrate DB * docs: add note for native code logic * test: ignore type bug
- Loading branch information
Showing
18 changed files
with
1,268 additions
and
411 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
{ | ||
"diagnostics": { | ||
"globals": [ | ||
"a", | ||
"describe", | ||
"it", | ||
"vim" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,136 +1,43 @@ | ||
local sqlite = require "sqlite" | ||
local log = require "plenary.log" | ||
|
||
---@diagnostic disable: missing-return, unused-local | ||
---@class FrecencyDatabaseConfig | ||
---@field root string | ||
|
||
---@class FrecencySqlite: sqlite_db | ||
---@field files sqlite_tbl | ||
---@field timestamps sqlite_tbl | ||
|
||
---@class FrecencyFile | ||
---@field count integer | ||
---@field id integer | ||
---@field path string | ||
---@field score integer calculated from count and age | ||
|
||
---@class FrecencyTimestamp | ||
---@field age integer calculated from timestamp | ||
---@field file_id integer | ||
---@field id integer | ||
---@field timestamp number | ||
|
||
---@class FrecencyDatabaseGetFilesOptions | ||
---@field path string? | ||
---@field workspace string? | ||
|
||
---@class FrecencyDatabase | ||
---@field config FrecencyDatabaseConfig | ||
---@field private buf_registered_flag_name string | ||
---@field private fs FrecencyFS | ||
---@field private sqlite FrecencySqlite | ||
---@field has_entry fun(): boolean | ||
---@field new fun(fs: FrecencyFS, config: FrecencyDatabaseConfig): FrecencyDatabase | ||
---@field protected fs FrecencyFS | ||
local Database = {} | ||
|
||
---@param fs FrecencyFS | ||
---@param config FrecencyDatabaseConfig | ||
---@return FrecencyDatabase | ||
Database.new = function(fs, config) | ||
local lib = sqlite.lib --[[@as sqlite_lib]] | ||
local self = setmetatable( | ||
{ config = config, buf_registered_flag_name = "telescope_frecency_registered", fs = fs }, | ||
{ __index = Database } | ||
) | ||
self.sqlite = sqlite { | ||
uri = self.config.root .. "/file_frecency.sqlite3", | ||
files = { id = true, count = { "integer", default = 1, required = true }, path = "string" }, | ||
timestamps = { | ||
id = true, | ||
file_id = { "integer", reference = "files.id", on_delete = "cascade" }, | ||
timestamp = { "real", default = lib.julianday "now" }, | ||
}, | ||
} | ||
return self | ||
end | ||
|
||
---@return boolean | ||
function Database:has_entry() | ||
return self.sqlite.files:count() > 0 | ||
end | ||
|
||
---@param paths string[] | ||
---@return integer | ||
function Database:insert_files(paths) | ||
if #paths == 0 then | ||
return 0 | ||
end | ||
---@param path string | ||
return self.sqlite.files:insert(vim.tbl_map(function(path) | ||
return { path = path, count = 0 } -- TODO: remove when sql.nvim#97 is closed | ||
end, paths)) | ||
end | ||
---@return nil | ||
function Database:insert_files(paths) end | ||
|
||
---@param workspace string? | ||
---@return FrecencyFile[] | ||
function Database:get_files(workspace) | ||
local query = workspace and { contains = { path = { workspace .. "/*" } } } or {} | ||
log.debug { query = query } | ||
return self.sqlite.files:get(query) | ||
end | ||
---@return integer[]|string[] | ||
function Database:unlinked_entries() end | ||
|
||
---@param datetime string? ISO8601 format string | ||
---@return FrecencyTimestamp[] | ||
function Database:get_timestamps(datetime) | ||
local lib = sqlite.lib | ||
local age = lib.cast((lib.julianday(datetime) - lib.julianday "timestamp") * 24 * 60, "integer") | ||
return self.sqlite.timestamps:get { keys = { age = age, "id", "file_id" } } | ||
end | ||
---@param files integer[]|string[] | ||
---@return nil | ||
function Database:remove_files(files) end | ||
|
||
---@param path string | ||
---@return integer: id of the file entry | ||
---@return boolean: whether the entry is inserted (true) or updated (false) | ||
function Database:upsert_files(path) | ||
local file = self.sqlite.files:get({ where = { path = path } })[1] --[[@as FrecencyFile?]] | ||
if file then | ||
self.sqlite.files:update { where = { id = file.id }, set = { count = file.count + 1 } } | ||
return file.id, false | ||
end | ||
return self.sqlite.files:insert { path = path }, true | ||
end | ||
|
||
---@param file_id integer | ||
---@param datetime string? ISO8601 format string | ||
---@return integer | ||
function Database:insert_timestamps(file_id, datetime) | ||
return self.sqlite.timestamps:insert { | ||
file_id = file_id, | ||
timestamp = datetime and sqlite.lib.julianday(datetime) or nil, | ||
} | ||
end | ||
|
||
---@param file_id integer | ||
---@param max_count integer | ||
function Database:trim_timestamps(file_id, max_count) | ||
local timestamps = self.sqlite.timestamps:get { where = { file_id = file_id } } --[[@as FrecencyTimestamp[] ]] | ||
local trim_at = timestamps[#timestamps - max_count + 1] | ||
if trim_at then | ||
self.sqlite.timestamps:remove { file_id = tostring(file_id), id = "<" .. tostring(trim_at.id) } | ||
end | ||
end | ||
|
||
---@return integer[] | ||
function Database:unlinked_entries() | ||
---@param file FrecencyFile | ||
return self.sqlite.files:map(function(file) | ||
if not self.fs:is_valid_path(file.path) then | ||
return file.id | ||
end | ||
end) | ||
end | ||
|
||
---@param ids integer[] | ||
---@param datetime string? | ||
---@return nil | ||
function Database:remove_files(ids) | ||
self.sqlite.files:remove { id = ids } | ||
end | ||
function Database:update(path, max_count, datetime) end | ||
|
||
return Database | ||
---@async | ||
---@class FrecencyDatabaseEntry | ||
---@field ages number[] | ||
---@field count integer | ||
---@field path string | ||
---@field score number | ||
|
||
---@param workspace string? | ||
---@param datetime string? | ||
---@return FrecencyDatabaseEntry[] | ||
function Database:get_entries(workspace, datetime) end |
Oops, something went wrong.