A database ORM for the nelua programming language
- nelua
- postgres
- mysql
- sqlite
NB: The db requirements are only specific to whatever databases you make use of in your program
## NORM_DB = "SQLITE"
local norm = require ".norm"
-- Instantiate a new Db object
local db, err = norm.Db.new({
name = "test.db"
})
-- Create and run your Db migrations
local schema = norm.Schema
local type = schema.ColumnType
local migrations: hashmap(string, norm.Schema.MigrationFn)
migrations["1"] = function(db: norm.Db): string
local err = schema.create_table(db, "users", {
{"id", type.integer, { primary_key = true }},
{"name", type.text, { unique = true }},
{"age", type.integer}
})
return err
end
local err = schema.migrate(db, migrations)
-- Insert a value into the users table
local users_insert: hashmap(string, string)
users_insert["name"] = "James"
users_insert["age"] = "21"
local rows, err = db:insert("users", users_insert)
assert(err == "", err)
-- Select matching rows from the users table
local users_select: hashmap(string, string)
users_select["name"] = "James"
users_select["age"] = "21"
local rows, err = db:select("* FROM users", users_select)
assert(err == "", err)
print(rows[1]["name"]) -- Should print "James"
-- Delete matching rows from the user table
local users_delete: hashmap(string, string)
users_delete["name"] = "James"
users_delete["age"] = "21"
local err = db:delete("users", users_delete)
assert(err == "", err)
local model = norm.Model
-- Create a new User model
local Users, err = model.new(db, "users", "Users")
assert(err == "", err)
-- Create a new model instance
local users_create: hashmap(string, string)
users_create["name"] = "James"
users_create["age"] = "21"
local user, err = Users:create(users_create)
assert(err == "", err)
-- Get a column from the model instance
local name, err = user:get_col("name")
assert(err == "", err)
print(name) -- Should print "James"
-- Delete the User model instance
local err = user:delete()
assert(err == "", err)
The norm module
local norm = @record{}
See base.nelua
local norm.destroy_rows = base.destroy_rows
See base.nelua
local norm.escape_identifier = base.escape_identifier
See base.nelua
local norm.escape_literal = base.escape_literal
This record is used to configure the ORM for the specified Database
## if NORM_DB == "SQLITE" then
local norm.Config = @record{
name: string,
log: logger.NotSetOrBool
}
## elseif NORM_DB == "PG" then
local norm.Config = @record{
name: string,
user: string,
password: string,
host: string,
port: uinteger,
log: logger.NotSetOrBool
}
## elseif NORM_DB == "MYSQL" then
local norm.Config = @record{
name: string,
user: string,
password: string,
host: string,
port: uinteger,
log: logger.NotSetOrBool
}
## end
This module is a collection of functions to make queries to the database
## if NORM_DB == "SQLITE" then
local norm.Db = @record{
conn: *sqlite.type,
log: logger.NotSetOrBool,
models: hashmap(string, pointer) -- norm.Model
}
## elseif NORM_DB == "PG" then
local norm.Db = @record{
conn: *pg.type,
log: logger.NotSetOrBool,
models: hashmap(string, pointer) -- norm.Model
}
## elseif NORM_DB == "MYSQL" then
local norm.Db = @record{
conn: *mysql.type,
log: logger.NotSetOrBool,
models: hashmap(string, pointer) -- norm.Model
}
## end
This function returns a new norm.Db object and an error string If no error occurs, an empty string is returned
function norm.Db.new(conf: norm.Config): (norm.Db, string)
This function cleans up the memory used by the Db object
function norm.Db:destroy()
Sends a query to the database returning a sequence(rows) of hashmap(columns) and a string In the case of an error, a non empty string is returned describing the error All further Db functions are constructed around this function
function norm.Db:query(sql: string): (sequence(hashmap(string, string)), string)
This function checks if a table exists in the database returning a true
if it does, false
otherwise
function norm.Db:table_exists(name: string): boolean
This function at it's simplest just appends "SELECT" to a query
Optionally, you can pass where
to the query which will append a WHERE
clause at the end of the sql with your conditions
function norm.Db:select(sql: string, where: hashmap(string, string)): (sequence(hashmap(string, string)), string)
This function inserts values
into the table tbl_name
returning the specified row of columns
If returning
is not set, and empty sequence is returned
function norm.Db:insert(
tbl_name: string,
values: hashmap(string, string),
returning: facultative(string)
): (sequence(hashmap(string, string)), string)
This function attempts deletes rows from a table tbl_name based on
conditions, returning an error string If condition is a string, it is appended in the
WHEREclause directly If condition is a hashmap, it is formatterd into a
WHERE` clause
If no error occurs, the string is empty
function norm.Db:update(
tbl_name: string,
values: hashmap(string, string),
conditions: overload(string, hashmap(string, string)),
returning: facultative(string)
): (sequence(hashmap(string, string)), string)
This function attempts deletes rows from a table tbl_name based on
conditions, returning an error string If condition is a string, it is appended in the
WHEREclause directly If condition is a hashmap, it is formatterd into a
WHERE` clause
If no error occurs, the string is empty
function norm.Db:delete(tbl_name: string, conditions: overload(string, hashmap(string, string))): string
This is a collection of types and function for creating and managing your database schema
local norm.Schema = @record{}
Type alias for what a migration function is in norm.Schema.migrate
local norm.Schema.MigrationFn = @function(db: norm.Db): string
These are options used to modify how a column is generated in norm.Schema.Column
local norm.Schema.ColumnOpts = @record{
default_val: string,
is_null: boolean,
unique: boolean,
primary_key: boolean
}
This defines the type of a column in norm.Schema.Column
local norm.Schema.ColumnType: type = @enum{
not_set = 0,
-- generic types
integer,
blob,
text,
numeric,
real,
varchar,
any,
----------
-- pg/mysql specific types
timestamp,
date,
----------
-- pg specific types
serial,
----------
-- mysql specific types
id,
---------
}
This defines the type of a column in norm.Schema.Column
local norm.Schema.Column = @record{
name: string,
type: norm.Schema.ColumnType,
opts: norm.Schema.ColumnOpts
}
These are options used to customise the creation of a table, see norm.Schema.create_table
if_not_exists
: Changes the table creation prefix to "CREATE TABLE IF NOT EXISTS"strict
: For sqlite, appends "STRICT" immediately after the closing)
to make the table a strict tableextra_sql
is appended before the final ) of the create query
local norm.Schema.CreateTableOpts = @record{
if_not_exists: boolean,
extra_sql: string,
strict: boolean
}
This function creates a table, name
, with the specified columns
returning an error string
If no error occurs, the string is empty
function norm.Schema.create_table(
db: norm.Db,
name: string,
columns: sequence(norm.Schema.Column),
opts: norm.Schema.CreateTableOpts
): string
This function drops a table, name
, returning an error string
If no error occurs, the string is empty
function norm.Schema.drop_table(db: norm.Db, name: string): string
These are options used to customise the creation of an index in a table, see norm.Schema.create_index
local norm.Schema.IndexOpts = @record{
where: string,
unique: boolean,
if_not_exists: boolean
}
This function adds new indexes to a table, name
, returning an error string
It takes a list of cols
and optional opts
to create a new index
If no error occurs, the string is empty
function norm.Schema.create_index(db: norm.Db, name: string, cols: sequence(string), opts: norm.Schema.IndexOpts): string
This function drops an index from a table, name
, returning an error string
It takes a list of cols
to determine the index to drop
If no error occurs, the string is empty
function norm.Schema.drop_index(db: norm.Db, name: string, cols: sequence(string)): string
This function adds a new col
to a table, tbl_name
, returning an error string
If no error occurs, the string is empty
function norm.Schema.add_column(db: norm.Db, tbl_name: string, col: norm.Schema.Column): string
This function drops a col
from a table, tbl_name
, returning an error string
If no error occurs, the string is empty
function norm.Schema.drop_column(db: norm.Db, tbl_name: string, col_name: string): string
This function renames a col
from a table, tbl_name
, replacing the old_col_name
with the new_col_name
, returning an error string
If no error occurs, the string is empty
function norm.Schema.rename_column(db: norm.Db, tbl_name: string, old_col_name: string, new_col_name: string): string
This function renames a table replacing the old_tbl_name
with the new_tbl_name
, returning an error string
If no error occurs, the string is empty
function norm.Schema.rename_table(db: norm.Db, old_tbl_name: string, new_tbl_name: string): string
This function runs all migrations
, returning an error string
If no error occurs, the string is empty
function norm.Schema.migrate(db: norm.Db, migrations: hashmap(string, norm.Schema.MigrationFn)): string
This enum is used to determine the type of relation in norm.Relation
local norm.RelationKind = @enum{
not_set = 0,
belongs_to,
has_one,
has_many
}
This record is used to define new relations for a model
local norm.Relation = @record{
kind: norm.RelationKind,
rel: record{
name: string,
model_name: string,
key: string
}
}
This is used to configure extra options for a model instance
primary_keys
: By default all models expect the table to have a primary key called "id" at index 1. This can be changed by setting the primary_keys value hererels
: A sequence of defined relations used to connect models
local norm.ModelOpts = @record{
primary_keys: sequence(string),
rels: sequence(norm.Relation)
}
This is used to interact with a particular table directly with more specific functions
local norm.Model = @record{
db: *norm.Db,
model_name: string,
tbl_name: string,
primary_keys: sequence(string),
rels: sequence(norm.Relation)
}
Used to get model instances split by pages
local norm.Model.OffsetPaginator = @record{
parent: *norm.Model,
per_page: uinteger,
fields: sequence(string),
order_by: string,
order: string,
where: string
}
Model Instance This is meant to represent a single row in a model's table
local norm.Model.Inst = @record{
parent: *norm.Model,
row: hashmap(string, string)
}
This function returns a new Model object and an error string If no error occurs, the string is empty
This function allocates memory for the model as it needs to be stored as a pointer in the db instance to be used with relations Maked sure to call Model:destroy to clear it out from the db before trying to create a new one with similar relations
function norm.Model.new(db: *norm.Db, tbl_name: string, model_name: string, opts: norm.ModelOpts): (*norm.Model, string)
Used to clean up memory created by the model and clear it from the model cache in the db instance
function norm.Model:destroy()
This function attempts to find a row based on the conditions
, returning a norm.model.Inst object and an error string
If condition is a string, it is appended in a WHERE
clause directly
If condition is a hashmap, it is formatterd into a WHERE
clause
If no error occurs, the string is empty
function norm.Model:find(conditions: overload(string, hashmap(string, string))): (norm.Model.Inst, string)
This function returns the total number of records in a model based on the provided conditions and an error string
If condition is a string, it is appended in a WHERE
clause directly
If condition is a hashmap, it is formatterd into a WHERE
clause
If no condition is provided then every row in the table will be counted
If no error occurs, the string is empty
function norm.Model:count(conditions: overload(niltype, string, hashmap(string, string))): (integer, string)
fields
: Set the returned fields of the query, default is "*"
extra_sql
: Appened at the end of the generated WHERE
clause
local norm.Model.SelectOpts = @record{
fields: sequence(string),
extra_sql: string
}
This function attempts to find rows based on the conditions
, returning a sequence of norm.model.Inst objects and an error string
If condition is a string, it is appended in a WHERE
clause directly
If condition is a hashmap, it is formatterd into a WHERE
clause
If no condition is provided then every row in the table will be returned
If no error occurs, the string is empty
function norm.Model:select(conditions: overload(niltype, string, hashmap(string, string)), opts: norm.Model.SelectOpts): (sequence(norm.Model.Inst), string)
per_page
- Maximum number of instances returned per get_page request
order_by
- Field to order the request by, default is "id"
order
- How to order the request, default is "ASC"
fields
- Fields to be returned by the request, default is "*"
local norm.Model.OffsetPaginatorOpts = @record{
per_page: uinteger,
order_by: string,
order: string,
fields: sequence(string)
}
This function returns a new Model OffsetPaginator object and an error string
If condition is a string, it is appended in a WHERE
clause directly
If condition is a hashmap, it is formatterd into a WHERE
clause
If no error occurs, the string is empty
function norm.Model:offset_paginated(conditions: overload(hashmap(string, string), string), opts: norm.Model.OffsetPaginatorOpts): (norm.Model.OffsetPaginator, string)
This function gets page
, where pages are 1 indexed returning the relevant sequence of model instances and an error string
If no error occurs, the string is empty
function norm.Model.OffsetPaginator:get_page(page: uinteger): (sequence(norm.Model.Inst), string)
This function returns the total number of items based of the paginators where clause and an error string If no error occurs, the string is empty
function norm.Model.OffsetPaginator:total_items(): (integer, string)
This function returns the total number of pages based of the paginators where clause and an error string If no error occurs, the string is empty
function norm.Model.OffsetPaginator:total_pages(): (integer, string)
This function inserts values
into the model's table, returning a norm.model.Inst object and an error string
If returning
is not set, and empty object is returned
If no error occurs, the string is empty
function norm.Model:create(values: hashmap(string, string), returning: facultative(string)): (norm.Model.Inst, string)
This function get's the value of the col by name
if it exists, returning the value and an error string
If no error occurs, the string is empty
function norm.Model.Inst:get_col(name: string): (string, string)
This function updates the row instance with values
into the model's table, returning a norm.model.Inst object and an error string
If returning
is not set, and empty object is returned
If no error occurs, the string is empty
function norm.Model.Inst:update(values: hashmap(string, string), returning: facultative(string)): (norm.Model.Inst, string)
This function deletes the row instance from the model's table, returning an error string If no error occurs, the string is empty
function norm.Model.Inst:delete(): string
This function returns a new model instance based on the rel_name
and an error string
If no error occurs, the string is empty
A relation that fetches a single related model instance. The foreign key column used to fetch the other model is located on the same table as the model. For example, a table named posts
with a column named user_id
would belong to a table named users
.
function norm.Model.Inst:get_belongs_to(rel_name: string): (norm.Model.Inst, string)
This function returns a new model instance based on the rel_name
and an error string
If no error occurs, the string is empty
A relation that fetches a single related model. Similar to belongs_to
, but the foreign key used to fetch the other model is located on the other table
function norm.Model.Inst:get_has_one(rel_name: string): (norm.Model.Inst, string)
This function returns a sequence of new model instances based on the rel_name
and an error string
If no error occurs, the string is empty
A relation that fetches a sequence of the related model. Similar to has_one
, but returning multiple
function norm.Model.Inst:get_has_many(rel_name: string): (sequence(norm.Model.Inst), string)
This file provides some utility functions used by the library
Escapes a string s
so it can be used as an identifier in sql queries
function base.escape_identifier(s: string)
Escapes a string s
so it can be used as a literal in sql queries
If the whole of s
matches an integer or float, it is not escaped
function base.escape_literal(s: string)
Returns a date string formatted properly for insertion in the database.
The time
argument is optional, will default to the current UTC time.
function base.format_date(time: facultative(integer))
Cleans up memory for rows returned from running sql queries
function base.destroy_rows(rows: sequence(hashmap(string, string)))
The logger module
local logger = @record{}
Used to determine if the logger should print to console or not
local logger.NotSetOrBool = @enum{
NOT_SET = 0,
TRUE,
FALSE
}
If the log
is not set or set to true, will print s
function logger.log(log: logger.NotSetOrBool, s: string)