Skip to content

Commit 2d84d32

Browse files
committed
🚧 modify and query sqlite_tbl by index
1 parent c7035b6 commit 2d84d32

File tree

7 files changed

+235
-61
lines changed

7 files changed

+235
-61
lines changed

Diff for: lua/sqlite/assert.lua

+17
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ local errors = {
1313
missing_db_object = "%s's db object is not set. set it with `%s:set_db(db)` and try again.",
1414
outdated_schema = "`%s` does not exists in {`%s`}, schema is outdateset `self.db.tbl_schemas[table_name]` or reload",
1515
auto_alter_more_less_keys = "schema defined ~= db schema. Please drop `%s` table first or set ensure to false.",
16+
miss_match_pk_type = "Primary key ('%s') is of type '%s', '%s' can't be used to access %s table. RECEIVED KEY: %s",
17+
no_primary_key = "%s has no primary key.",
1618
}
1719

1820
for key, value in pairs(errors) do
@@ -80,4 +82,19 @@ M.auto_alter_should_have_equal_len = function(len_new, len_old, tname)
8082
end
8183
end
8284

85+
M.should_match_pk_type = function(name, kt, pk, key)
86+
local knotstr = kt ~= "string"
87+
local knotnum = kt ~= "number"
88+
local pt = pk.type
89+
if not pk or not pk.type then
90+
return error(errors.no_primary_key:format(name))
91+
end
92+
93+
if knotstr and (pt == "string" or pt == "text") or knotnum and (pt == "number" or pt == "integer") then
94+
return error(errors.miss_match_pk_type:format(pk.name, pk.type, kt, name, key))
95+
end
96+
97+
return true
98+
end
99+
83100
return M

Diff for: lua/sqlite/defs.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ ffi.cdef [[
579579
]]
580580

581581
---@class sqlite3 @sqlite3 db object
582-
---@class sqlite_blob @sqlite3 blob object
582+
---@class sqlite_blob* @sqlite3 blob object
583583

584584
M.to_str = function(ptr, len)
585585
if ptr == nil then

Diff for: lua/sqlite/helpers.lua

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ end
6666
---@param o sqlite_tbl
6767
---@return any
6868
M.run = function(func, o)
69+
func = func or function() end
6970
a.should_have_db_object(o.db, o.name)
7071
local exec = function()
7172
local valid_schema = o.tbl_schema and next(o.tbl_schema) ~= nil

Diff for: lua/sqlite/tbl.lua

+9-14
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
local u = require "sqlite.utils"
1212
local h = require "sqlite.helpers"
1313

14+
local indexer = require "sqlite.tbl.indexer"
1415
local sqlite = {}
1516

1617
---@type sqlite_tbl
1718
sqlite.tbl = {}
1819
sqlite.tbl.__index = sqlite.tbl
1920

21+
-- TODO: Add examples to index access in sqlite.tbl.new
22+
2023
---Create new |sqlite_tbl| object. This object encouraged to be extend and
2124
---modified by the user. overwritten method can be still accessed via
2225
---pre-appending `__` e.g. redefining |sqlite_tbl:get|, result in
@@ -49,27 +52,19 @@ sqlite.tbl.__index = sqlite.tbl
4952
---@return sqlite_tbl
5053
function sqlite.tbl.new(name, schema, db)
5154
schema = schema or {}
52-
53-
local t = setmetatable({
55+
schema = u.if_nil(schema.schema, schema)
56+
---@type sqlite_tbl
57+
local tbl = setmetatable({
5458
db = db,
5559
name = name,
56-
tbl_schema = u.if_nil(schema.schema, schema),
60+
tbl_schema = schema,
5761
}, sqlite.tbl)
5862

5963
if db then
60-
h.run(function() end, t)
64+
h.run(nil, tbl)
6165
end
6266

63-
return setmetatable({}, {
64-
__index = function(_, key, ...)
65-
if type(key) == "string" then
66-
key = key:sub(1, 2) == "__" and key:sub(3, -1) or key
67-
if t[key] then
68-
return t[key]
69-
end
70-
end
71-
end,
72-
})
67+
return indexer(tbl)
7368
end
7469

7570
---Create or change table schema. If no {schema} is given,

Diff for: lua/sqlite/tbl/indexer.lua

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
local a = require "sqlite.assert"
2+
local u = require "sqlite.utils"
3+
---Get primary key from sqlite_tbl schema
4+
---@param tbl sqlite_schema_dict
5+
local get_primary_key = function(tbl)
6+
local pk
7+
for k, v in pairs(tbl or {}) do
8+
if type(v) == "table" and v.primary then
9+
pk = v
10+
if not pk.type then
11+
pk.type = pk[1]
12+
end
13+
pk.name = k
14+
break
15+
elseif type(v) == "boolean" and k ~= "ensure" then
16+
pk = {
17+
name = k,
18+
type = "integer",
19+
primary = true,
20+
}
21+
break
22+
end
23+
end
24+
25+
return pk
26+
end
27+
28+
---TODO: remove after using value should rawset and rawget in helpers.run, sqlite.tbl
29+
local is_part_of_tbl_object = function(key)
30+
return key == "db"
31+
or key == "tbl_exists"
32+
or key == "db_schema"
33+
or key == "tbl_name"
34+
or key == "tbl_schema"
35+
or key == "mtime"
36+
or key == "has_content"
37+
or key == "_name"
38+
end
39+
40+
local resolve_meta_key = function(kt, key)
41+
return kt == "string" and (key:sub(1, 2) == "__" and key:sub(3, -1) or key) or key
42+
end
43+
44+
---Used to extend a table row to be able to manipulate the row values
45+
---@param tbl sqlite_tbl
46+
---@param pk sqlite_schema_key
47+
---@return function(row, altkey):table
48+
local tbl_row_extender = function(tbl, pk)
49+
-- print(vim.inspect(pk))
50+
return function(row, reqkey)
51+
row = row or {}
52+
local mt = {
53+
__newindex = function(_, key, val)
54+
tbl:update { -- TODO: maybe return inserted row??
55+
where = { [pk.name] = (row[pk.name] or reqkey) },
56+
set = { [key] = val },
57+
}
58+
row = {}
59+
end,
60+
__index = function(_, key)
61+
if key == "values" then
62+
return row
63+
end
64+
if not row[key] then
65+
local res = tbl:where { [pk.name] = row[pk.name] } or {}
66+
row = res
67+
return res[key]
68+
else
69+
return row[key]
70+
end
71+
end,
72+
}
73+
74+
return setmetatable({}, mt)
75+
end
76+
end
77+
78+
local sep_query_and_where = function(q, keys)
79+
local kv = { where = {} }
80+
for k, v in pairs(q) do
81+
if u.contains(keys, k) then
82+
kv.where[k] = v
83+
else
84+
kv[k] = v
85+
end
86+
end
87+
88+
if next(kv.where) == nil then
89+
kv.where = nil
90+
end
91+
return kv
92+
end
93+
return function(tbl)
94+
local pk = get_primary_key(tbl.tbl_schema)
95+
local extend = tbl_row_extender(tbl, pk)
96+
local tbl_keys = u.keys(tbl.tbl_schema)
97+
local mt = {}
98+
99+
mt.__index = function(_, arg)
100+
if is_part_of_tbl_object(arg) then
101+
return tbl[arg]
102+
end
103+
104+
local kt = type(arg)
105+
local skey = resolve_meta_key(kt, arg)
106+
107+
if not pk or (kt == "string" and tbl[skey]) then
108+
return tbl[skey]
109+
end
110+
111+
if kt == "string" or kt == "number" and pk then
112+
a.should_match_pk_type(tbl.name, kt, pk, arg)
113+
return extend(tbl:where { [pk.name] = arg }, arg)
114+
end
115+
116+
return kt == "table" and tbl:get(sep_query_and_where(arg, tbl_keys))
117+
end
118+
119+
mt.__newindex = function(o, arg, val)
120+
if is_part_of_tbl_object(arg) then
121+
tbl[arg] = val
122+
return
123+
end
124+
125+
local kt, vt = type(arg), type(val)
126+
127+
if not pk or (vt == "function" and kt == "string") then
128+
rawset(o, arg, val)
129+
return
130+
end
131+
132+
if vt == "nil" and kt == "string" then
133+
tbl:remove { [pk.name] = arg }
134+
end
135+
136+
if vt == "table" and kt == "table" then
137+
local q = sep_query_and_where(arg, tbl_keys)
138+
q.set = val
139+
tbl:update(q)
140+
return
141+
end
142+
143+
if vt == "table" and pk then
144+
a.should_match_pk_type(tbl.name, kt, pk, arg)
145+
return tbl:update { where = { [pk.name] = arg }, set = val }
146+
end
147+
end
148+
149+
return setmetatable({ config = {} }, mt)
150+
end

Diff for: lua/sqlite/utils.lua

+9
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ M.okeys = function(t)
5959
return r
6060
end
6161

62+
M.contains = function(tbl, val)
63+
for _, v in ipairs(tbl) do
64+
if v == val then
65+
return true
66+
end
67+
end
68+
return false
69+
end
70+
6271
M.opairs = (function()
6372
local __gen_order_index = function(t)
6473
local orderedIndex = {}

Diff for: test/auto/tbl_spec.lua

+48-46
Original file line numberDiff line numberDiff line change
@@ -974,72 +974,74 @@ describe("sqlite.tbl", function()
974974
end)
975975

976976
describe(":index access", function()
977-
-- local db_path = "/tmp/idx_db"
977+
local db_path = ":memory:" or "/tmp/idx_db"
978978
-- vim.loop.fs_unlink(db_path)
979-
db = sql:open()
979+
db = sql:open(db_path)
980980

981981
describe("string_index:", function()
982-
local kvpair = tbl("kvpair", {
982+
local kv = tbl("kvpair", {
983983
key = { "text", primary = true, required = true, unique = true },
984-
value = "integer",
984+
len = "integer",
985985
}, db)
986986

987987
it("access/insert-to table using primary key", function()
988-
kvpair.a = { value = 1 }
989-
eq({ key = "a", value = 1 }, kvpair.a)
988+
kv.a = { len = 1 }
989+
eq({ key = "a", len = 1 }, kv.a)
990990
end)
991991

992-
it("access/update a row field value", function()
993-
kvpair.a.value = 2
994-
eq(2, kvpair.where({ value = 2 }).value, "should have been set")
995-
eq(2, kvpair.a.value, "should have been set")
996-
kvpair.a.value = 3
997-
eq({ key = "a", value = 3 }, kvpair.a, "should return values")
992+
it("access/update a row field len", function()
993+
-- eq({}, kv.a)
994+
kv.a = { len = 1 }
995+
kv.a.len = 2
996+
eq(2, kv:where({ len = 2 }).len, "should have been set")
997+
eq(2, kv.a.len, "should have been set")
998+
kv.a.len = 3
999+
eq({ key = "a", len = 3 }, kv.a, "should return values")
9981000
end)
9991001

10001002
it("remove a row using primary key", function()
1001-
kvpair.a = nil
1002-
eq(nil, kvpair.where { key = "a" }, "should be empty")
1003-
eq({}, kvpair.a, "should be empty")
1003+
kv.a = nil
1004+
eq(nil, kv:where { key = "a" }, "should be empty")
1005+
eq({}, kv.a, "should be empty")
10041006
end)
10051007

1006-
it("sets a row field value without creating the row first", function()
1007-
kvpair["some key with spaces :D"].value = 4
1008-
eq(kvpair["some key with spaces :D"], { key = "some key with spaces :D", value = 4 })
1009-
kvpair["some key with spaces :D"] = nil
1008+
it("sets a row field len without creating the row first", function()
1009+
kv["some key with spaces :D"].len = 4
1010+
eq(kv["some key with spaces :D"], { key = "some key with spaces :D", len = 4 })
1011+
kv["some key with spaces :D"] = nil
10101012
end)
10111013

10121014
it("query using index", function()
1013-
kvpair.a.value, kvpair.b.value, kvpair.c.value = 1, 2, 3
1015+
kv.a.len, kv.b.len, kv.c.len = 1, 2, 3
10141016
eq(
1015-
{
1016-
{ key = "a", value = 1 },
1017-
{ key = "b", value = 2 },
1018-
},
1019-
kvpair[{
1020-
where = { value = { 1, 2, 3 } },
1021-
order_by = { asc = { "key", "value" } },
1022-
limit = 2,
1023-
}]
1017+
{
1018+
{ key = "a", len = 1 },
1019+
{ key = "b", len = 2 },
1020+
},
1021+
kv[{
1022+
where = { len = { 1, 2, 3 } },
1023+
order_by = { asc = { "key", "len" } },
1024+
limit = 2,
1025+
}]
10241026
)
10251027
end)
1026-
-- it("bulk update", function()
1027-
-- kvpair[{ value = { 1, 2, 3 } }] = { value = 10 }
1028-
-- eq(
1029-
-- {
1030-
-- { key = "a", value = 10 },
1031-
-- { key = "b", value = 10 },
1032-
-- },
1033-
-- kvpair[{
1034-
-- order_by = { asc = { "key" } },
1035-
-- limit = 2,
1036-
-- }]
1037-
-- )
1038-
-- end)
1039-
end)
1040-
1041-
-- vim.loop.fs_unlink(db_path)
1042-
end)
1028+
it("bulk update", function()
1029+
kv[{ len = { 1, 2, 3 } }] = { len = 10 }
1030+
eq(
1031+
{
1032+
{ key = "a", len = 10 },
1033+
{ key = "b", len = 10 },
1034+
},
1035+
kv[{
1036+
order_by = { asc = { "key" } },
1037+
limit = 2,
1038+
}]
1039+
)
1040+
end)
1041+
end)
1042+
1043+
-- vim.loop.fs_unlink(db_path)
1044+
end)
10431045

10441046
clean()
10451047
end)

0 commit comments

Comments
 (0)