Lua 4Days ORM 10 minutes tutorial
Database configuration
Before the beginning you should add some simple settings to your database configuration. You must create some global variable DB:
DB = {}Development configurations:
-
new- if this value istrue, then previous database was removed and new was created (trueby default). -
backtrace- if this value istrue, than you will be able to see in console all Warnings, Errors and Information messages (trueby default). -
DEBUG- if this value istrue, you will be able to see in console all SQL queries (trueby default).
Database configurations
-
type- by default"sqlite3". Also it can be:-
"mysql"- for MySQL database -
"postgres"- for PostgreSQL database (implemented soon)
-
-
name- this is a path to database file for"sqlite3". For other databases this value contains database name. (by default"database.db") -
username- database user name (by defaultnil) -
password- database password (by defaultnil) -
host- database host (by defaultnil) -
port- database host port (by defaultnil)
After setting configurations you can add 2 modules import to your file
local Table = require("orm.model")
local fields = require("orm.tools.fields")Create table
local User = Table({
__tablename__ = "user",
username = fields.CharField({max_length = 100, unique = true}),
password = fields.CharField({max_length = 50, unique = true}),
age = fields.IntegerField({max_length = 2, null = true}),
job = fields.CharField({max_length = 50, null = true}),
time_create = fields.DateTimeField({null = true})
})For every table is created a column id with PRIMARY KEY field by default.
__tablename__ is required value which should contain the name of the table.
Also you can add different settings to your table fields
-
max_length- it is a maximum allowable value of symbols that you can use in a string -
unique- if this value istruethen all the column's values are unique -
null- can betrueorfalse. If value istruethen value in table will be saved asNULL. -
default- if you didn't add any value to this field - it is going to be saved as default value. -
primary_key- If you want to add some value asprimary key, you can set this value astrue.
Types of table fields
Supported types of table fields
-
CharField- CreatesVARCHARfield -
IntegerField- CreatesINTEGERfield -
TextField- CreatesTEXTfield -
BooleanField- CreatesBOOLEANfield -
DateTimeField- CreatesINTEGERfield but brings backos.dateinstance -
PrimaryField- CreatesINTEGERfield withPRIMARY KEY -
ForeignKey- Creates relationships between tables.
Also you can create your types of table fields. But about it later.
Create data
Try to create a new user:
local user = User({
username = "Bob Smith",
password = "SuperSecretPassword",
time_create = os.time()
})Now you created new user, but it was not added to database. You can add him.
user:save()Now this user with all the information is in database. We can get his id
print("User " .. user.username .. " has id " .. user.id)
-- User Bob Smith has id 1Update data
You can change your data:
user.username = "John Smith"This value was changed in model, but it has not been changed in database table.
user:save()Now try to get new username for user:
print("New user name is " .. user.username) -- New user name is John Smith You have updated in database only the column that you changed. You can also edit columns for the value by another terms:
User.get:where({time_create__null = true})
:update({time_create = os.time()})The conditions will be described in the next chapter
Remove data
And also you can remove your data from table.
user:delete()You can also delete columns for the value by another terms:
-- add test user
user = User({username = "SomebodyNew", password = "NotSecret"})
user:save()
User.get:where({username = "SomebodyNew"}):delete()The conditions will be described in the next chapter
Get data
Also we can get data from table. But before this let's create 5 test users.
user = User({username = "First user", password = "secret1", age = 22})
user:save()
user = User({username = "Second user", password = "secret_test", job = "Lua developer"})
user:save()
user = User({username = "Another user", password = "old_test", age = 44})
user:save()
user = User({username = "New user", password = "some_passwd", age = 23, job = "Manager"})
user:save()
user = User({username = "Old user", password = "secret_passwd", age = 44})
user:save()And now try get one of them:
local first_user = User.get:first()
print("First user name is: " .. first_user.username)
-- First user name is: First userBut also we can get all users from table:
local users = User.get:all()
print("We get " .. users:count() .. " users")
-- We get 5 usersMethod count returns number of users in the list.
Limit and Offset
Sometime we need to get not one but not all users. For the first, try to get first 2 users from the table.
users = User.get:limit(2):all()
print("We get " .. users:count() .. " users")
-- We get 2 users
print("Second user name is: " .. users[2].username)
-- Second user name is: Second userGreat! But if we want to get next two users? We can do this by using following example:
users = User.get:limit(2):offset(2):all()
print("Second user name is: " .. users[2].username)
-- Second user name is: New userOrder result
Also you can sort your result by order. We want to sort users from the oldest to the youngest.
users = User.get:order_by({desc('age')}):all()
print("First user id: " .. users[1].id)
-- First user id: 3But we have 2 users with age 44. We can order them by name.
users = User.get:order_by({desc('age'), asc('username')}):all()You can order your table query by other parameters too.
Group result
And now try to group your results:
users = User.get:group_by({'age'}):all()
print('Find ' .. users:count() ..' users')
-- Find 4 usersWhere and Having
These two methods have the same syntax. But having you can use only with group_bymethod. There's one simple example:
user = User.get:where({username = "First user"}):first()
print("User id is: " .. user.id) -- User id is: 1And the same for having:
users = User.get:group_by({'id'}):having({age = 44}):all()
print("We get " .. users:count() .. " users with age 44")
-- We get 2 users with age 44Great! But what if we need to do more operations than just a differentiation of table fields. We can do that! This is the list with some rules:
For example we use for default colname. It can be any column in your model
-
colname = value- the same ascolname = value -
colname__lt = value- the same ascolname < value(valuemust be a number) -
colname__lte = value- the same ascolname <= value(valuemust be a number) -
colname__gt = value- the same ascolname > value(valuemust be a number) -
colname__gte = value- the same ascolname >= value(valuemust be a number) -
colname__in = {v1, v2,...,vn}- the same ascolname in (value1, value2,...,vn)(vncan be number, string) -
colname__notin = {v1, v2,...,vn}- the same ascolname not in (value1, value2,...,vn)(vncan be number, string) -
colname__null = value- if value istruethen result iscolname is NULL, but if value isfalsethen result iscolname is not NULL
Super SELECT
But if we do ...
user = User.get:where({age__lt = 30,
age__lte = 30,
age__gt = 10,
age__gte = 10
})
:order_by({asc('id')})
:group_by({'age', 'password'})
:having({id__in = {1, 3, 5},
id__notin = {2, 4, 6},
username__null = false
})
:limit(2)
:offset(1)
:all()This example doesn't make sense. But it works!
JOIN TABLES
Now we can create a join of tables. But before that we create some table with foreign key column:
local News = Table({
__tablename__ = "group",
title = fields.CharField({max_length = 100, unique = false, null = false}),
text = fields.TextField({null = true}),
create_user_id = fields.ForeignKey({to = User})
})And add two test news:
local user = User.get:first()
local news = News({title = "Some news", create_user_id = user.id})
news:save()
news = News({title = "Other title", create_user_id = user.id})
news:save()Now try to get all the news from the owner.
local news = News.get:join(User):all()
print("First news user id is: " .. news[1].user.id) -- First news user id is: 1But if we want to get all users and also to get three news for each user . We can do this by following example:
local user = User.get:join(News):first()
print("User " .. user.id .. " has " .. user.news_all:count() .. " news")
-- User 1 has 2 news
for _, user_news in pairs(user.news_all) do
print(user_news.title)
end
-- Some news
-- Other titleIf you want to get all the values from tables you can combine table's names and prefix "_all". Like in previous example
user.news_allnews_all - returns a list of all news for current user or nil if news does not exist.
Create column types
We can create a field type for every table. Try to create EmailField type:
fields.EmailField = fields:register({
__type__ = "varchar",
settings = {
max_length = 100
},
validator = function (value)
return value:match("[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w%w?%w?")
end,
to_type = function (value)
return value
end,
as = function (value)
return "'" .. value .. "'"
end
})Let's make it step by step:
__type__ - this variable creates the appropriate type in the database ("varchar", "integer", "boolean", "date", "datetime", "text", ...).
By default this value is "varchar".
settings -set a field value as default (fields settings was describe later). By default this value is empty.
validator - validates the value of the variable. If value is correct - returns true. If value is not correct it returns false and doesn't update or add rows. By default it always returns true.
to_type - parses value for correct sql save. By default it is not parsed value
as - returns the value from lua to SQL. By default it is not parsed value.
local UserEmails = Table({
__tablename__ = "user_emails",
email = fields.EmailField(),
user_id = fields.ForeignKey({to = User})
})
local user_email = UserEmails({
email = "mailexample.com",
user_id = user.id
})
user_email:save() -- This email wasn't added
-- And try again
local user_email = UserEmails({
email = "mail@example.com",
user_id = user.id
})
user_email:save() -- This email was added
user_email.email = "not email"
user_email:save()-- This email wasn't updated
user_email.email = "valid@email.com"
user_email:save() -- This email was updatedFinal
All code you can see in example.lua file. Feel free to use it! Good luck!