ORM
V has a powerful, concise ORM baked in! Create tables, insert records, manage relationships, all regardless of the DB driver you decide to use.
Nullable
For a nullable column, use an option field. If the field is non-option, the column will be defined
with NOT NULL
at table creation.
struct Foo {
notnull string
nullable ?string
}
Attributes
Structs
[table: 'name']
explicitly sets the name of the table for the struct
Fields
-
[primary]
sets the field as the primary key -
[unique]
gives the field aUNIQUE
constraint -
[unique: 'foo']
adds the field to aUNIQUE
group -
[skip]
or[sql: '-']
field will be skipped -
[sql: type]
wheretype
is a V type such asint
orf64
-
[serial]
or[sql: serial]
lets the DB backend choose a column type for an auto-increment field -
[sql: 'name']
sets a custom column name for the field -
[sql_type: 'SQL TYPE']
explicitly sets the type in SQL -
[default: 'raw_sql']
insertsraw_sql
verbatim in a "DEFAULT" clause when creating a new table, allowing for SQL functions likeCURRENT_TIME
. For raw strings, surroundraw_sql
with backticks (`). -
[fkey: 'parent_id']
sets foreign key for an field which holds an array
Usage
Here are a couple example structs showing most of the features outlined above.
import time
@[table: 'foos']
struct Foo {
id int @[primary; sql: serial]
name string
created_at time.Time @[default: 'CURRENT_TIME']
updated_at ?string @[sql_type: 'TIMESTAMP']
deleted_at ?time.Time
children []Child @[fkey: 'parent_id']
}
struct Child {
id int @[primary; sql: serial]
parent_id int
name string
}
To use the ORM, there is a special interface that lets you use the structs and V itself in queries. This interface takes the database instance as an argument.
import db.sqlite
db := sqlite.connect(':memory:')!
sql db {
// query; see below
}!
When you need to reference the table, simply pass the struct itself.
import models.Foo
struct Bar {
id int @[primary; sql: serial]
}
sql db {
create table models.Foo
create table Bar
}!
Create & Drop Tables
You can create and drop tables by passing the struct to create table
and drop table
.
import models.Foo
struct Bar {
id int @[primary; sql: serial]
}
sql db {
create table models.Foo
drop table Bar
}!
Insert Records
To insert a record, create a struct and pass the variable to the query. Again, reference the struct as the table.
foo := Foo{
name: 'abc'
created_at: time.now()
// updated_at defaults to none
// deleted_at defaults to none
children: [
Child{
name: 'abc'
},
Child{
name: 'def'
},
]
}
foo_id := sql db {
insert foo into Foo
}!
If the id
field is marked as sql: serial
and primary
, the insert expression
returns the database ID of the newly added object. Getting an ID of a newly
added DB row is often useful.
When inserting, [sql: serial]
fields, and fields with a [default: 'raw_sql']
attribute, are not sent to the database when the value being sent is the default
for the V struct field (e.g., 0 int, or an empty string). This allows the
database to insert default values for auto-increment fields and where you have
specified a default.
Select
You can select rows from the database by passing the struct as the table, and use V syntax and functions for expressions. Selecting returns an array of the results.
result := sql db {
select from Foo where id == 1
}!
foo := result.first()
result := sql db {
select from Foo where id > 1 && name != 'lasanha' limit 5
}!
result := sql db {
select from Foo where id > 1 order by id
}!
Update
You can update fields in a row using V syntax and functions. Again, pass the struct as the table.
sql db {
update Foo set updated_at = time.now() where name == 'abc' && updated_at is none
}!
Note that is none
and !is none
can be used to select for NULL fields.
Delete
You can delete rows using V syntax and functions. Again, pass the struct as the table.
sql db {
delete from Foo where id > 10
}!
time.Time Fields
It's definitely useful to cast a field as time.Time
so you can use V's built-in time functions;
however, this is handled a bit differently than expected in the ORM. time.Time
fields are
created as integer columns in the database. Because of this, the usual time functions
(current_timestamp
, NOW()
, etc) in SQL do not work as defaults.
Example
import db.pg
struct Member {
id string @[default: 'gen_random_uuid()'; primary; sql_type: 'uuid']
name string
created_at string @[default: 'CURRENT_TIMESTAMP'; sql_type: 'TIMESTAMP']
}
fn main() {
db := pg.connect(pg.Config{
host: 'localhost'
port: 5432
user: 'user'
password: 'password'
dbname: 'dbname'
})!
defer {
db.close()
}
sql db {
create table Member
}!
new_member := Member{
name: 'John Doe'
}
sql db {
insert new_member into Member
}!
selected_members := sql db {
select from Member where name == 'John Doe' limit 1
}!
john_doe := selected_members.first()
sql db {
update Member set name = 'Hitalo' where id == john_doe.id
}!
}