mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
vweb: implement database pool (#18010)
This commit is contained in:
parent
6f85384f7f
commit
5f870f41b5
@ -6,7 +6,7 @@ The [gitly](https://gitly.org/) site is based on vweb.
|
||||
|
||||
**_Some features may not be complete, and have some bugs._**
|
||||
|
||||
## Getting start
|
||||
## Quick Start
|
||||
Just run **`v new <name> web`** in your terminal
|
||||
|
||||
## Features
|
||||
@ -16,6 +16,7 @@ Just run **`v new <name> web`** in your terminal
|
||||
- **Easy to deploy** just one binary file that also includes all templates. No need to install any
|
||||
dependencies.
|
||||
- **Templates are precompiled** all errors are visible at compilation time, not at runtime.
|
||||
- **Multithreaded** by default
|
||||
|
||||
### Examples
|
||||
|
||||
@ -391,6 +392,96 @@ pub fn (mut app App) not_found() vweb.Result {
|
||||
}
|
||||
```
|
||||
|
||||
### Databases
|
||||
The `db` field in a vweb app is reserved for database connections. The connection is
|
||||
copied to each new request.
|
||||
|
||||
**Example:**
|
||||
|
||||
```v
|
||||
module main
|
||||
|
||||
import vweb
|
||||
import db.sqlite
|
||||
|
||||
struct App {
|
||||
vweb.Context
|
||||
mut:
|
||||
db sqlite.DB
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// create the database connection
|
||||
mut db := sqlite.connect('db')!
|
||||
|
||||
vweb.run(&App{
|
||||
db: db
|
||||
}, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
### Multithreading
|
||||
By default, a vweb app is multithreaded, that means that multiple requests can
|
||||
be handled in parallel by using multiple CPU's: a worker pool. You can
|
||||
change the number of workers (maximum allowed threads) by altering the `nr_workers`
|
||||
option. The default behaviour is to use the maximum number of jobs (cores in most cases).
|
||||
|
||||
**Example:**
|
||||
```v ignore
|
||||
fn main() {
|
||||
// assign a maximum of 4 workers
|
||||
vweb.run_at(&App{}, nr_workers: 4)
|
||||
}
|
||||
```
|
||||
|
||||
#### Database Pool
|
||||
A single connection database works fine if you run your app with 1 worker, of if
|
||||
you access a file-based database like a sqlite file.
|
||||
|
||||
This approach will fail when using a non-file based database connection like a mysql
|
||||
connection to another server somewhere on the internet. Multiple threads would need to access
|
||||
the same connection at the same time.
|
||||
|
||||
To resolve this issue, you can use the vweb's built-in database pool. The database pool
|
||||
will keep a number of connections open when the app is started and each worker is
|
||||
assigned its own connection.
|
||||
|
||||
Let's look how we can improve our previous example with database pooling and using a
|
||||
postgresql server instead.
|
||||
|
||||
**Example:**
|
||||
```v
|
||||
module main
|
||||
|
||||
import vweb
|
||||
import db.pg
|
||||
|
||||
struct App {
|
||||
vweb.Context
|
||||
db_handle vweb.DatabasePool[pg.DB]
|
||||
mut:
|
||||
db pg.DB
|
||||
}
|
||||
|
||||
fn get_database_connection() pg.DB {
|
||||
// insert your own credentials
|
||||
return pg.connect(user: 'user', password: 'password', dbname: 'database') or { panic(err) }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// create the database pool and pass our `get_database_connection` function as handler
|
||||
pool := vweb.database_pool(handler: get_database_connection)
|
||||
|
||||
// no need to set the `db` field
|
||||
vweb.run(&App{
|
||||
db_handle: pool
|
||||
}, 8080)
|
||||
}
|
||||
```
|
||||
|
||||
If you don't use the default number of workers (`nr_workers`) you have to change
|
||||
it to the same number in `vweb.run_at` as in `vweb.database_pool`
|
||||
|
||||
### Controllers
|
||||
Controllers can be used to split up app logic so you are able to have one struct
|
||||
per `"/"`. E.g. a struct `Admin` for urls starting with `"/admin"` and a struct `Foo`
|
||||
@ -458,7 +549,8 @@ There will be an error, because the controller `Admin` handles all routes starti
|
||||
|
||||
#### Databases and `[vweb_global]` in controllers
|
||||
|
||||
Fields with `[vweb_global]` like a database have to passed to each controller individually.
|
||||
Fields with `[vweb_global]` have to passed to each controller individually.
|
||||
The `db` field is unique and will be treated as a `vweb_global` field at all times.
|
||||
|
||||
**Example:**
|
||||
```v
|
||||
@ -470,14 +562,14 @@ import db.sqlite
|
||||
struct App {
|
||||
vweb.Context
|
||||
vweb.Controller
|
||||
pub mut:
|
||||
db sqlite.DB [vweb_global]
|
||||
mut:
|
||||
db sqlite.DB
|
||||
}
|
||||
|
||||
struct Admin {
|
||||
vweb.Context
|
||||
pub mut:
|
||||
db sqlite.DB [vweb_global]
|
||||
mut:
|
||||
db sqlite.DB
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -494,6 +586,50 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Using a database pool
|
||||
|
||||
**Example:**
|
||||
```v
|
||||
module main
|
||||
|
||||
import vweb
|
||||
import db.pg
|
||||
|
||||
struct App {
|
||||
vweb.Context
|
||||
vweb.Controller
|
||||
db_handle vweb.DatabasePool[pg.DB]
|
||||
mut:
|
||||
db pg.DB
|
||||
}
|
||||
|
||||
struct Admin {
|
||||
vweb.Context
|
||||
db_handle vweb.DatabasePool[pg.DB]
|
||||
mut:
|
||||
db pg.DB
|
||||
}
|
||||
|
||||
fn get_database_connection() pg.DB {
|
||||
// insert your own credentials
|
||||
return pg.connect(user: 'user', password: 'password', dbname: 'database') or { panic(err) }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// create the database pool and pass our `get_database_connection` function as handler
|
||||
pool := vweb.database_pool(handler: get_database_connection)
|
||||
|
||||
mut app := &App{
|
||||
db_handle: pool
|
||||
controllers: [
|
||||
vweb.controller('/admin', &Admin{
|
||||
db_handle: pool
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
#### - set_status
|
||||
|
@ -371,7 +371,16 @@ pub fn (ctx &Context) get_header(key string) string {
|
||||
return ctx.req.header.get_custom(key) or { '' }
|
||||
}
|
||||
|
||||
pub type DatabasePool[T] = fn (tid int) T
|
||||
|
||||
interface DbPoolInterface {
|
||||
db_handle voidptr
|
||||
mut:
|
||||
db voidptr
|
||||
}
|
||||
|
||||
interface DbInterface {
|
||||
mut:
|
||||
db voidptr
|
||||
}
|
||||
|
||||
@ -425,7 +434,7 @@ pub fn controller[T](path string, global_app &T) &ControllerPath {
|
||||
path: path
|
||||
handler: fn [global_app, path, routes] [T](ctx Context, mut url urllib.URL, tid int) {
|
||||
// request_app is freed in `handle_route`
|
||||
mut request_app := new_request_app[T](global_app, ctx)
|
||||
mut request_app := new_request_app[T](global_app, ctx, tid)
|
||||
// transform the url
|
||||
url.path = url.path.all_after_first(path)
|
||||
handle_route[T](mut request_app, url, &routes, tid)
|
||||
@ -514,7 +523,7 @@ pub fn run_at[T](global_app &T, params RunParams) ! {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_request_app[T](global_app &T, ctx Context) &T {
|
||||
fn new_request_app[T](global_app &T, ctx Context, tid int) &T {
|
||||
// Create a new app object for each connection, copy global data like db connections
|
||||
mut request_app := &T{}
|
||||
$if T is MiddlewareInterface {
|
||||
@ -522,9 +531,15 @@ fn new_request_app[T](global_app &T, ctx Context) &T {
|
||||
middlewares: global_app.middlewares.clone()
|
||||
}
|
||||
}
|
||||
$if T is DbInterface {
|
||||
|
||||
$if T is DbPoolInterface {
|
||||
// get database connection from the connection pool
|
||||
request_app.db = global_app.db_handle(tid)
|
||||
} $else $if T is DbInterface {
|
||||
// copy a database to a app without pooling
|
||||
request_app.db = global_app.db
|
||||
}
|
||||
|
||||
$for field in T.fields {
|
||||
if field.is_shared {
|
||||
unsafe {
|
||||
@ -622,7 +637,7 @@ fn handle_conn[T](mut conn net.TcpConn, global_app &T, routes &map[string]Route,
|
||||
}
|
||||
}
|
||||
|
||||
mut request_app := new_request_app(global_app, ctx)
|
||||
mut request_app := new_request_app(global_app, ctx, tid)
|
||||
handle_route(mut request_app, url, routes, tid)
|
||||
}
|
||||
|
||||
@ -995,3 +1010,25 @@ fn (mut w Worker[T]) process_incomming_requests() {
|
||||
eprintln('[vweb] closing worker ${w.id}.')
|
||||
}
|
||||
}
|
||||
|
||||
[params]
|
||||
pub struct PoolParams[T] {
|
||||
handler fn () T [required]
|
||||
nr_workers int = runtime.nr_jobs()
|
||||
}
|
||||
|
||||
// database_pool creates a pool of database connections
|
||||
pub fn database_pool[T](params PoolParams[T]) DatabasePool[T] {
|
||||
mut connections := []T{}
|
||||
// create a database connection for each worker
|
||||
for _ in 0 .. params.nr_workers {
|
||||
connections << params.handler()
|
||||
}
|
||||
|
||||
return fn [connections] [T](tid int) T {
|
||||
$if vweb_trace_worker_scan ? {
|
||||
eprintln('[vweb] worker ${tid} received database connection')
|
||||
}
|
||||
return connections[tid]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user