Lua table as frontend to database - lua

I am attempting to implement a database as a Lua table. Using metatables, this table would be empty, and when an item is requested or modified in the table, it would return or modify the item in the database. The database itself would never be loaded into memory, except for the parts which are requested. It should be interacted with by the program as a table (as it is a table). The table, since it's only a "front", would save modified data to the database (rather than defining that item in the table).
In a table with no tables inside of it, this is easy to implement. I'm trying to make it work with a multi-layered table of indefinite depth.
(Aside: The database I'm considering is redis. Ideally this could be implemented for any database or database-like server by only changing the basic operating syntax.)
Because of the behavior of Lua's metatables, the __newindex method is only used when something is modified in the top-level (or created, if you're using a proxy). The __index method is called when something is read, even if the call is to modify something within a sub-table. Because of this, I'm trying to write an __index method that, when a table is requested, returns yet another pseudo-proxy (a table proxying a database rather than another tables) with the same behavior, except the proxy is for the table/array/list within the top-level, etc, to an indefinite depth. I am struggling.
My questions are:
Has this been implemented before?
Should I be focusing on "proper"
class system rather than what I'm doing now?

When you create a table, simply add an empty table in the fake and set it's metatable:
local fake = {}
do
local lookup = {} --Will be using this to avoid using lots of metatables
local real = {}
local meta
meta = {
__index = function(self,i)
return rawget(lookup[self], i)
end,
__newindex = function(self,i,v)
rawset(lookup[self], i, v)
if type(v) == "table" then
rawset(self, i, setmetatable({},meta))
lookup[self[i]] = v
end
end
}
setmetatable(fake, meta)
lookup[fake] = real
end
fake[1] = "hello"
print(fake[1])
print(rawget(fake, 1))
fake.x = {"hi"}
print(fake.x)
print(rawget(fake, 'x')) --This still prints a table because there actually is one, but in reality it's abiding by our rules
print(fake.x[1])
print(rawget(fake.x, 1))
fake.x.y = "aha"
print(fake.x.y)
print(rawget(fake.x, 'y'))
The only caveat with this method is they can directly modify the database like so:
fake.myvalue = {}
fake.myvalue = 5
Another method could be to wrap as you go:
local fake = {}
do
local lookup = {} --Will be using this to avoid using lots of metatables
local cache = {} --will be using to avoid usings tons of new objects
local real = {}
local meta
meta = {
__index = function(self,i)
local val = rawget(lookup[self], i)
if type(val) == "table" then
if cache[val] then
return cache[val]
else
local faker = {}
lookup[faker] = val
cache[val] = faker
return setmetatable(faker, meta)
end
else
return val
end
end,
__newindex = function(self,i,v)
rawset(lookup[self], i, v)
end
}
setmetatable(fake, meta)
lookup[fake] = real
end
fake[1] = "hello"
print(fake[1])
print(rawget(fake, 1))
fake.x = {"hi"}
print(fake.x)
print(rawget(fake, 'x')) --This still prints a table because there actually is one, but in reality it's abiding by our rules
print(fake.x[1])
print(rawget(fake.x, 1))
fake.x.y = "aha"
print(fake.x.y)
print(rawget(fake.x, 'y'))
Which avoids the direct modifying problem completely

Related

Using Insert with a large multi-layered table using Lua

So I am working on a script for GTA5 and I need to transfer data over to a js script. However so I don't need to send multiple arrays to js I require a table, the template for the table should appear as below.
The issue I'm having at the moment is in the second section where I receive all vehicles and loop through each to add it to said 'vehicleTable'. I haven't been able to find the "table.insert" method used in a multilayered table
So far I've tried the following
table.insert(vehicleTable,vehicleTable[class][i][vehicleName])
This seems to store an 'object'(table)? so it does not show up when called in the latter for loop
Next,
vehicleTable = vehicleTable + vehicleTable[class][i][vehicleName]
This seemed like it was going nowhere as I either got a error or nothing happened.
Next,
table.insert(vehicleTable,class)
table.insert(vehicleTable[class],i)
table.insert(vehicleTable[class][i],vehicleName)
This one failed on the second line, I'm unsure why however it didn't even reach the next problem I saw later which would be the fact that line 3 had no way to specify the "Name" field.
Lastly the current one,
local test = {[class] = {[i]={["Name"]=vehicleName}}}
table.insert(vehicleTable,test)
It works without errors but ultimately it doesn't file it in the table instead it seems to create its own branch so object within the object.
And after about 3 hours of zero progress on this topic I turn to the stack overflow for assistance.
local vehicleTable = {
["Sports"] = {
[1] = {["Name"] = "ASS", ["Hash"] = "Asshole2"},
[2] = {["Name"] = "ASS2", ["Hash"] = "Asshole1"}
},
["Muscle"] = {
[1] = {["Name"] = "Sedi", ["Hash"] = "Sedina5"}
},
["Compacts"] = {
[1] = {["Name"] = "MuscleCar", ["Hash"] = "MCar2"}
},
["Sedan"] = {
[1] = {["Name"] = "Blowthing", ["Hash"] = "Blowthing887"}
}
}
local vehicles = GetAllVehicleModels();
for i=1, #vehicles do
local class = vehicleClasses[GetVehicleClassFromName(vehicles[i])]
local vehicleName = GetLabelText(GetDisplayNameFromVehicleModel(vehicles[i]))
print(vehicles[i].. " " .. class .. " " .. vehicleName)
local test = {[class] = {[i]={["Name"]=vehicleName}}}
table.insert(vehicleTable,test)
end
for k in pairs(vehicleTable) do
print(k)
-- for v in pairs(vehicleTable[k]) do
-- print(v .. " " .. #vehicleTable[k])
-- end
end
If there is not way to add to a library / table how would I go about sorting all this without needing to send a million (hash, name, etc...) requests to js?
Any recommendations or support would be much appreciated.
Aside the fact that you do not provide the definition of multiple functions and tables used in your code that would be necessary to provide a complete answere without making assumptions there are many misconceptions regarding very basic topics in Lua.
The most prominent is that you don't know how to use table.insert and what it can do. It will insert (append by default) a numeric field to a table. Given that you have non-numeric keys in your vehicleTable this doesn't make too much sense.
You also don't know how to use the + operator and that it does not make any sense to add a table and a string.
Most of your code seems to be the result of guess work and trial and error.
Instead of referring to the Lua manual so you know how to use table.insert and how to index tables properly you spend 3 hours trying all kinds of variations of your incorrect code.
Assuming a vehicle model is a table like {["Name"] = "MyCar", ["Hash"] = "MyCarHash"} you can add it to a vehicle class like so:
table.insert(vehicleTable["Sedan"], {["Name"] = "MyCar", ["Hash"] = "MyCarHash"})
This makes sense because vehicleTable.Sedan has numeric indices. And after that line it would contain 2 cars.
Read the manual. Then revisit your code and fix your errors.

LUA - Create Meta Table with every sub table beeing a meta table again

but this will get confusing for sure.
I'm still very new to LUA and one thing i haven't worked much yet with is metatables.
I need to find a way to create a meta table which runs a function on editing values. This is not a problem if i stay on "one level", so the first index. Then i can simply use the __newindex to run it. But what i'm trying to do is to run the function whenever any value is changed.
This would require some way to set any table inside the metatable to be again a metatable running the same function as the "main" metatable
In my use case that would be a "save" function:
function MySaveFunction(tbl)
FileSave(my_settings_path, tbl)
end
MyTable = setmetatable()
MyTable.Value = value --> run MySaveFunction(MyTable.Value)
MyTable.SubTable = {} --> run setmetatable() on SubTable
MyTable.SubTable.Value = value --> run MySaveFunction(MyTable.SubTable.Value)
MyTable.SubTable.SubSubTable = {} --> run setmetatable() on SubSubTable
MyTable.SubTable.SubSubTable.Value = value --> run MySaveFunction(MyTable.SubTable.SubSubTable.Value)
MyTable.SubTable.SubSubSubTable = {} --> run setmetatable() on SubSubSubTable
MyTable.SubTable.SubSubSubTable.Value = value --> run MySaveFunction(MyTable.SubTable.SubSubSubTable.Value)
Hope someone can help me <.<
First thing to take a note of is that __newindex and __index metamethods are only triggered when they handle nil value in target table. If you want to track every single change, you can't just use __newindex, because once you write a value, subsequent calls won't do anything. Instead, you need to use a proxy table.
Another important thing to consider is tracking paths of accessed members.
Last, but not least, you need to remember to use raw access functions like e.g. rawget when implementing your handlers. Otherwise, you may encounter stack overflows or other weird behaviour.
Let's have a trivial example to illustrate the problems:
local mt = {}
function mt.__newindex (t, key, value)
if type(value) == "table" then
rawset(t, key, setmetatable(value, mt)) -- Set the metatable for nested table
-- Using `t[key] = setmetatable(value, mt)` here would cause an overflow.
else
print(t, key, "=", value) -- We expect to see output in stdout for each write
rawset(t, key, value)
end
end
local root = setmetatable({}, mt)
root.first = 1 -- table: 0xa40c30 first = 1
root.second = 2 -- table: 0xa40c30 second = 2
root.nested_table = {} -- /nothing/
root.nested_table.another = 4 -- table: 0xa403a0 another = 4
root.first = 5 -- /nothing/
Now, we need to deal with them. Let's start with a way to create a proxy table:
local
function make_proxy (data)
local proxy = {}
local metatable = {
__index = function (_, key) return rawget(data, key) end,
__newindex = function (_, key, value)
if type(value) == "table" then
rawset(data, key, make_proxy(value))
else
print(data, key, "=", value) -- Or your save function here!
rawset(data, key, value)
end
end
}
return setmetatable(proxy, metatable) -- setmetatable() simply returns `proxy`
end
This way you have three tables: proxy, metatable and data. User accesses proxy, but because it's completely empty on each access either __index or __newindex metamethods from metatable are called. Those handlers access data table to retrieve or set the actual values that user is interested in.
Run this in the same way as previously and you will get an improvement:
local root = make_proxy{}
root.first = 1 -- table: 0xa40c30 first = 1
root.second = 2 -- table: 0xa40c30 second = 2
root.nested_table = {} -- /nothing/
root.nested_table.another = 4 -- table: 0xa403a0 another = 4
root.first = 5 -- table: 0xa40c30 first = 5
This should give you an overview on why you should use a proxy table here and how to handle metamethods for it.
What's left is how to identify the path of the field that you are accessing. That part is covered in another answer to another question. I don't see a reason to duplicate it.

I need clarification on Metatable.__index

I asked earlier why my methods for a metatable weren't being located by Lua, and was told that by setting __index to my metatable, that it would resolve the issue, so I assumed that a method when called was searching by index in the metatable, but I've ran into an issue now that I need to use indexing brackets [ and ] on my metatable, so __indexis assigned to return an index from a table inside of it, how do I resolve the functionality needs of both using methods, and use of indexing brackets
I wrote a minimal example indicating the problem:
TestMetatable = {DataTable = {}}
TestMetatable.__index = TestMetatable
function TestMetatable.new()
local Tmp = {}
setmetatable(Tmp,TestMetatable)
Tmp.DataTable = {1}
return Tmp
end
function TestMetatable:TestMethod()
print("Ran Successfully")
end
function TestMetatable.__index(self,index)
return self.DataTable[index]
end
local Test = TestMetatable.new()
-- both functionalities are needed
print(Test[1])
Test:TestMethod()
You need to understand the difference between __index and __newindex, and their relationship with the current contents of the main table.
__newindex is only called/accessed when all the following are true:
When you are setting a value into the main table, via tbl[index] = expr (or equivalent syntax, like tbl.name = expr).
When the key you are trying to set into the main table does not already exist in the main table.
The second one trips people up often. And that's your problem here, because __index is only accessed when:
When the key being read from the main table does not already exist in the main table.
So if you want to filter every read from and write to a table, then that table must always be empty. Therefore, those reads and writes need to go into some other table you create for each new object. So your new function needs to create two tables: one that remains empty and one that has all the data in it.
Honestly, I wish Lua had a way to create just an empty piece of userdata that you could bind a user-defined metatable to, just to avoid these issues.
the way I resolved this problem, according to Nicol Bolas's solution, if it might give clarity to anyone else's confusion :-)
TestMetatable = {DataTable = {}, FunctionTable = {}}
function TestMetatable.new()
local Tmp = {}
setmetatable(Tmp,TestMetatable)
Tmp.DataTable = {1}
Tmp.FunctionTable = TestMetatable
return Tmp
end
function TestMetatable:TestMethod()
print("Ran Successfully")
end
function TestMetatable.__index(self,index)
if type(index) == "string" then
return self.FunctionTable[index]
else
return self.DataTable[index]
end
end
local Test = TestMetatable.new()
-- both functionalities are needed
print(Test[1])
Test:TestMethod()

Using tables in other files

Edit I got this working, I'm not sure if this is the right way to do it, but this is what works right now
I just started learning Lua, and I'm trying to figure out how to pass tables between files so that I can have a more organized codespace. I have read through the book Programming in Lua, and for some reason, I can't figure out what i'm doing wrong.
The problem i'm getting is this error:
lua: Test2.lua:3: attempt to call method 'New' (a nil value)
From this code:
--Test.lua----------------
module("Test", package.seeall)
vector = require "./Hump/vector"
Bot = {}
Bot.position = vector.new(0,0)
function Bot:New(object)
object = object or {}
setmetatable(object, self)
self.__index = self
return object
end
--Test2.lua------------------
require "Test"
Bot1 = Test.Bot:New()
print(Bot1.position)
As far as I understand it, this error means that it cannot find the method new, it is effectively undefined. I thought that require imports the file in the path?
Bot is an empty table.
local B = {} -- initialize local B with new table
Bot = B -- Bot now references the same table as B
B = { position = vector.new(0,0) } -- here you create a NEW table, B ~= Bot now
function B:New(object) -- store New function in B table, Bot still empty
So you're returning an empty table.
No need for two variables here at all.
local Bot = {
-- stuff
}
function Bot:New(object)
-- stuff
end
return Bot

Lua metamethods not being called

I'm kinda new to Lua (not really done much with it yet) and I'm trying to wrap my mind around metatables. I have had them working before but now (after a number of months) I have encountered something really weird.
What should this script print when run?
__mt = {}
__mt.__index = function(table, key)
print("In __index")
return 99
end
test = {}
test.x = 5
setmetatable(test, __mt)
print(test.x)
Personally, I would expect it to print "In __index" (from the metamethod) followed by 99. However, whenever I run it I get 5. Nothing I do can get the index metamethod to run. It just acts like I'm using rawget() instead.
Curiously, adding
print(getmetatable(test).__index(test, "x"))
will do the right thing. The metatable is there, __index() is correct, it just isn't being called.
Is this a bug or am I just doing something stupid? I can't tell.
The metamethod (in old terminology also called fallback) called __index is only called if the key x does not exist in the table, when you access t.x. Try print(t.y) instead!
Added: Yes, using a proxy table.
function doubletable(T)
local store = T or {}
local mt = {}
mt.__index = function (t, k) return store[k] and 2*store[k] end
mt.__newindex = store
return setmetatable({}, mt)
end
t = doubletable({a=1, b=3})
t.c = 7
print(t.a, t.b, t.c)
-- output: 2 6 14

Resources