chore: rename db module to avoid conflict with vlib
This commit is contained in:
parent
b3a119f221
commit
91a976c634
19 changed files with 15 additions and 17 deletions
101
src/dbms/dbms.v
Normal file
101
src/dbms/dbms.v
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
module dbms
|
||||
|
||||
import db.sqlite
|
||||
import time
|
||||
|
||||
pub struct VieterDb {
|
||||
conn sqlite.DB
|
||||
}
|
||||
|
||||
struct MigrationVersion {
|
||||
id int [primary]
|
||||
version int
|
||||
}
|
||||
|
||||
const (
|
||||
migrations_up = [
|
||||
$embed_file('migrations/001-initial/up.sql'),
|
||||
$embed_file('migrations/002-rename-to-targets/up.sql'),
|
||||
$embed_file('migrations/003-target-url-type/up.sql'),
|
||||
$embed_file('migrations/004-nullable-branch/up.sql'),
|
||||
$embed_file('migrations/005-repo-path/up.sql'),
|
||||
]
|
||||
migrations_down = [
|
||||
$embed_file('migrations/001-initial/down.sql'),
|
||||
$embed_file('migrations/002-rename-to-targets/down.sql'),
|
||||
$embed_file('migrations/003-target-url-type/down.sql'),
|
||||
$embed_file('migrations/004-nullable-branch/down.sql'),
|
||||
$embed_file('migrations/005-repo-path/down.sql'),
|
||||
]
|
||||
)
|
||||
|
||||
// init initializes a database & adds the correct tables.
|
||||
pub fn init(db_path string) !VieterDb {
|
||||
conn := sqlite.connect(db_path)!
|
||||
|
||||
sql conn {
|
||||
create table MigrationVersion
|
||||
}
|
||||
|
||||
cur_version := sql conn {
|
||||
select from MigrationVersion limit 1
|
||||
}
|
||||
|
||||
// If there's no row yet, we add it here
|
||||
if cur_version == MigrationVersion{} {
|
||||
sql conn {
|
||||
insert cur_version into MigrationVersion
|
||||
}
|
||||
}
|
||||
|
||||
// Apply each migration in order
|
||||
for i in cur_version.version .. dbms.migrations_up.len {
|
||||
migration := dbms.migrations_up[i].to_string()
|
||||
|
||||
version_num := i + 1
|
||||
|
||||
// vfmt does not like these dots
|
||||
println('Applying migration ${version_num}' + '...')
|
||||
|
||||
// The sqlite library seems to not like it when multiple statements are
|
||||
// passed in a single exec. Therefore, we split them & run them all
|
||||
// separately.
|
||||
for part in migration.split(';').map(it.trim_space()).filter(it != '') {
|
||||
res := conn.exec_none(part)
|
||||
|
||||
if res != sqlite.sqlite_done {
|
||||
return error('An error occurred while applying migration ${version_num}: SQLite error code ${res}')
|
||||
}
|
||||
}
|
||||
|
||||
// The where clause doesn't really matter, as there will always only be
|
||||
// one entry anyways.
|
||||
sql conn {
|
||||
update MigrationVersion set version = version_num where id > 0
|
||||
}
|
||||
}
|
||||
|
||||
return VieterDb{
|
||||
conn: conn
|
||||
}
|
||||
}
|
||||
|
||||
// row_into<T> converts an sqlite.Row into a given type T by parsing each field
|
||||
// from a string according to its type.
|
||||
pub fn row_into[T](row sqlite.Row) T {
|
||||
mut i := 0
|
||||
mut out := T{}
|
||||
|
||||
$for field in T.fields {
|
||||
$if field.typ is string {
|
||||
out.$(field.name) = row.vals[i]
|
||||
} $else $if field.typ is int {
|
||||
out.$(field.name) = row.vals[i].int()
|
||||
} $else $if field.typ is time.Time {
|
||||
out.$(field.name) = time.unix(row.vals[i].int())
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
99
src/dbms/logs.v
Normal file
99
src/dbms/logs.v
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
module dbms
|
||||
|
||||
import models { BuildLog, BuildLogFilter }
|
||||
import time
|
||||
|
||||
// get_build_logs returns all BuildLog's in the database.
|
||||
pub fn (db &VieterDb) get_build_logs(filter BuildLogFilter) []BuildLog {
|
||||
mut where_parts := []string{}
|
||||
|
||||
if filter.target != 0 {
|
||||
where_parts << 'target_id == ${filter.target}'
|
||||
}
|
||||
|
||||
if filter.before != time.Time{} {
|
||||
where_parts << 'start_time < ${filter.before.unix_time()}'
|
||||
}
|
||||
|
||||
if filter.after != time.Time{} {
|
||||
where_parts << 'start_time > ${filter.after.unix_time()}'
|
||||
}
|
||||
|
||||
// NOTE: possible SQL injection
|
||||
if filter.arch != '' {
|
||||
where_parts << "arch == '${filter.arch}'"
|
||||
}
|
||||
|
||||
mut parts := []string{}
|
||||
|
||||
for exp in filter.exit_codes {
|
||||
if exp[0] == `!` {
|
||||
code := exp[1..].int()
|
||||
|
||||
parts << 'exit_code != ${code}'
|
||||
} else {
|
||||
code := exp.int()
|
||||
|
||||
parts << 'exit_code == ${code}'
|
||||
}
|
||||
}
|
||||
|
||||
if parts.len > 0 {
|
||||
where_parts << parts.map('(${it})').join(' or ')
|
||||
}
|
||||
|
||||
mut where_str := ''
|
||||
|
||||
if where_parts.len > 0 {
|
||||
where_str = 'where ' + where_parts.map('(${it})').join(' and ')
|
||||
}
|
||||
|
||||
query := 'select * from BuildLog ${where_str} limit ${filter.limit} offset ${filter.offset}'
|
||||
rows, _ := db.conn.exec(query)
|
||||
res := rows.map(row_into[BuildLog](it))
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// get_build_logs_for_target returns all BuildLog's in the database for a given
|
||||
// target.
|
||||
pub fn (db &VieterDb) get_build_logs_for_target(target_id int) []BuildLog {
|
||||
res := sql db.conn {
|
||||
select from BuildLog where target_id == target_id order by id
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// get_build_log tries to return a specific BuildLog.
|
||||
pub fn (db &VieterDb) get_build_log(id int) ?BuildLog {
|
||||
res := sql db.conn {
|
||||
select from BuildLog where id == id
|
||||
}
|
||||
|
||||
if res.id == 0 {
|
||||
return none
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// add_build_log inserts the given BuildLog into the database.
|
||||
pub fn (db &VieterDb) add_build_log(log BuildLog) int {
|
||||
sql db.conn {
|
||||
insert log into BuildLog
|
||||
}
|
||||
|
||||
// Here, this does work because a log doesn't contain any foreign keys,
|
||||
// meaning the ORM only has to do a single add
|
||||
inserted_id := db.conn.last_id() as int
|
||||
|
||||
return inserted_id
|
||||
}
|
||||
|
||||
// delete_build_log delete the BuildLog with the given ID from the database.
|
||||
pub fn (db &VieterDb) delete_build_log(id int) {
|
||||
sql db.conn {
|
||||
delete from BuildLog where id == id
|
||||
}
|
||||
}
|
||||
3
src/dbms/migrations/001-initial/down.sql
Normal file
3
src/dbms/migrations/001-initial/down.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE IF EXISTS BuildLog;
|
||||
DROP TABLE IF EXISTS GitRepoArch;
|
||||
DROP TABLE IF EXISTS GitRepo;
|
||||
22
src/dbms/migrations/001-initial/up.sql
Normal file
22
src/dbms/migrations/001-initial/up.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
CREATE TABLE IF NOT EXISTS GitRepo (
|
||||
id INTEGER PRIMARY KEY,
|
||||
url TEXT NOT NULL,
|
||||
branch TEXT NOT NULL,
|
||||
repo TEXT NOT NULL,
|
||||
schedule TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS GitRepoArch (
|
||||
id INTEGER PRIMARY KEY,
|
||||
repo_id INTEGER NOT NULL,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS BuildLog (
|
||||
id INTEGER PRIMARY KEY,
|
||||
repo_id INTEGER NOT NULL,
|
||||
start_time INTEGER NOT NULL,
|
||||
end_time iNTEGER NOT NULL,
|
||||
arch TEXT NOT NULL,
|
||||
exit_code INTEGER NOT NULL
|
||||
);
|
||||
5
src/dbms/migrations/002-rename-to-targets/down.sql
Normal file
5
src/dbms/migrations/002-rename-to-targets/down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE Target RENAME TO GitRepo;
|
||||
ALTER TABLE TargetArch RENAME TO GitRepoArch;
|
||||
|
||||
ALTER TABLE GitRepoArch RENAME COLUMN target_id TO repo_id;
|
||||
ALTER TABLE BuildLog RENAME COLUMN target_id TO repo_id;
|
||||
5
src/dbms/migrations/002-rename-to-targets/up.sql
Normal file
5
src/dbms/migrations/002-rename-to-targets/up.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE GitRepo RENAME TO Target;
|
||||
ALTER TABLE GitRepoArch RENAME TO TargetArch;
|
||||
|
||||
ALTER TABLE TargetArch RENAME COLUMN repo_id TO target_id;
|
||||
ALTER TABLE BuildLog RENAME COLUMN repo_id TO target_id;
|
||||
4
src/dbms/migrations/003-target-url-type/down.sql
Normal file
4
src/dbms/migrations/003-target-url-type/down.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- I'm not sure whether I should remove any non-git targets here. Keeping them
|
||||
-- will result in invalid targets, but removing them means losing data.
|
||||
ALTER TABLE Target DROP COLUMN kind;
|
||||
|
||||
1
src/dbms/migrations/003-target-url-type/up.sql
Normal file
1
src/dbms/migrations/003-target-url-type/up.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE Target ADD COLUMN kind TEXT NOT NULL DEFAULT 'git';
|
||||
26
src/dbms/migrations/004-nullable-branch/down.sql
Normal file
26
src/dbms/migrations/004-nullable-branch/down.sql
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
-- This down won't really work because it'll throw NOT NULL errors, but I'm
|
||||
-- just putting it here for future reference (still not sure whether I'm even
|
||||
-- gonna use these)
|
||||
PRAGMA foreign_keys=off;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE Target RENAME TO _Target_old;
|
||||
|
||||
CREATE TABLE Target (
|
||||
id INTEGER PRIMARY KEY,
|
||||
url TEXT NOT NULL,
|
||||
branch TEXT NOT NULL,
|
||||
repo TEXT NOT NULL,
|
||||
schedule TEXT,
|
||||
kind TEXT NOT NULL DEFAULT 'git'
|
||||
);
|
||||
|
||||
INSERT INTO Target (id, url, branch, repo, schedule, kind)
|
||||
SELECT id, url, branch, repo, schedule, kind FROM _Target_old;
|
||||
|
||||
DROP TABLE _Target_old;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
||||
23
src/dbms/migrations/004-nullable-branch/up.sql
Normal file
23
src/dbms/migrations/004-nullable-branch/up.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
PRAGMA foreign_keys=off;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE Target RENAME TO _Target_old;
|
||||
|
||||
CREATE TABLE Target (
|
||||
id INTEGER PRIMARY KEY,
|
||||
url TEXT NOT NULL,
|
||||
branch TEXT,
|
||||
repo TEXT NOT NULL,
|
||||
schedule TEXT,
|
||||
kind TEXT NOT NULL DEFAULT 'git'
|
||||
);
|
||||
|
||||
INSERT INTO Target (id, url, branch, repo, schedule, kind)
|
||||
SELECT id, url, branch, repo, schedule, kind FROM _Target_old;
|
||||
|
||||
DROP TABLE _Target_old;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
||||
1
src/dbms/migrations/005-repo-path/down.sql
Normal file
1
src/dbms/migrations/005-repo-path/down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE Target DROP COLUMN path;
|
||||
1
src/dbms/migrations/005-repo-path/up.sql
Normal file
1
src/dbms/migrations/005-repo-path/up.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE Target ADD COLUMN path TEXT;
|
||||
87
src/dbms/targets.v
Normal file
87
src/dbms/targets.v
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
module dbms
|
||||
|
||||
import models { Target, TargetArch }
|
||||
|
||||
// get_target tries to return a specific target.
|
||||
pub fn (db &VieterDb) get_target(target_id int) ?Target {
|
||||
res := sql db.conn {
|
||||
select from Target where id == target_id
|
||||
}
|
||||
|
||||
// If a select statement fails, it returns a zeroed object. By
|
||||
// checking one of the required fields, we can see whether the query
|
||||
// returned a result or not.
|
||||
if res.id == 0 {
|
||||
return none
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// add_target inserts the given target into the database.
|
||||
pub fn (db &VieterDb) add_target(target Target) int {
|
||||
sql db.conn {
|
||||
insert target into Target
|
||||
}
|
||||
|
||||
// ID of inserted target is the largest id
|
||||
inserted_target := sql db.conn {
|
||||
select from Target order by id desc limit 1
|
||||
}
|
||||
|
||||
return inserted_target.id
|
||||
}
|
||||
|
||||
// delete_target deletes the target with the given id from the database.
|
||||
pub fn (db &VieterDb) delete_target(target_id int) {
|
||||
sql db.conn {
|
||||
delete from Target where id == target_id
|
||||
delete from TargetArch where target_id == target_id
|
||||
}
|
||||
}
|
||||
|
||||
// update_target updates any non-array values for a given target.
|
||||
pub fn (db &VieterDb) update_target(target_id int, params map[string]string) {
|
||||
mut values := []string{}
|
||||
|
||||
// TODO does this allow for SQL injection?
|
||||
$for field in Target.fields {
|
||||
if field.name in params {
|
||||
// Any fields that are array types require their own update method
|
||||
$if field.typ is string {
|
||||
values << "${field.name} = '${params[field.name]}'"
|
||||
}
|
||||
}
|
||||
}
|
||||
values_str := values.join(', ')
|
||||
// I think this is actual SQL & not the ORM language
|
||||
query := 'update Target set ${values_str} where id == ${target_id}'
|
||||
|
||||
db.conn.exec_none(query)
|
||||
}
|
||||
|
||||
// update_target_archs updates a given target's arch value.
|
||||
pub fn (db &VieterDb) update_target_archs(target_id int, archs []TargetArch) {
|
||||
archs_with_id := archs.map(TargetArch{
|
||||
...it
|
||||
target_id: target_id
|
||||
})
|
||||
|
||||
sql db.conn {
|
||||
delete from TargetArch where target_id == target_id
|
||||
}
|
||||
|
||||
for arch in archs_with_id {
|
||||
sql db.conn {
|
||||
insert arch into TargetArch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// target_exists is a utility function that checks whether a target with the
|
||||
// given id exists.
|
||||
pub fn (db &VieterDb) target_exists(target_id int) bool {
|
||||
db.get_target(target_id) or { return false }
|
||||
|
||||
return true
|
||||
}
|
||||
129
src/dbms/targets_iter.v
Normal file
129
src/dbms/targets_iter.v
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
module dbms
|
||||
|
||||
import models { Target, TargetFilter }
|
||||
import db.sqlite
|
||||
|
||||
// Iterator providing a filtered view into the list of targets currently stored
|
||||
// in the database. It replaces functionality usually performed in the database
|
||||
// using SQL queries that can't currently be used due to missing stuff in V's
|
||||
// ORM.
|
||||
pub struct TargetsIterator {
|
||||
conn sqlite.DB
|
||||
filter TargetFilter
|
||||
window_size int = 32
|
||||
mut:
|
||||
window []Target
|
||||
window_index u64
|
||||
// Offset in entire list of unfiltered targets
|
||||
offset int
|
||||
// Offset in filtered list of targets
|
||||
filtered_offset u64
|
||||
started bool
|
||||
done bool
|
||||
}
|
||||
|
||||
// targets returns an iterator allowing filtered access to the list of targets.
|
||||
pub fn (db &VieterDb) targets(filter TargetFilter) TargetsIterator {
|
||||
window_size := 32
|
||||
|
||||
return TargetsIterator{
|
||||
conn: db.conn
|
||||
filter: filter
|
||||
window: []Target{cap: window_size}
|
||||
window_size: window_size
|
||||
}
|
||||
}
|
||||
|
||||
// advance_window moves the sliding window over the filtered list of targets
|
||||
// until it either reaches the end of the list of targets, or has encountered a
|
||||
// non-empty window.
|
||||
fn (mut ti TargetsIterator) advance_window() {
|
||||
for {
|
||||
ti.window = sql ti.conn {
|
||||
select from Target order by id limit ti.window_size offset ti.offset
|
||||
}
|
||||
ti.offset += ti.window.len
|
||||
|
||||
if ti.window.len == 0 {
|
||||
ti.done = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ti.filter.repo != '' {
|
||||
ti.window = ti.window.filter(it.repo == ti.filter.repo)
|
||||
}
|
||||
|
||||
if ti.filter.arch != '' {
|
||||
ti.window = ti.window.filter(it.arch.any(it.value == ti.filter.arch))
|
||||
}
|
||||
|
||||
if ti.filter.query != '' {
|
||||
ti.window = ti.window.filter(it.url.contains(ti.filter.query)
|
||||
|| it.path.contains(ti.filter.query) || it.branch.contains(ti.filter.query))
|
||||
}
|
||||
|
||||
// We break out of the loop once we found a non-empty window
|
||||
if ti.window.len > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// next returns the next target, if possible.
|
||||
pub fn (mut ti TargetsIterator) next() ?Target {
|
||||
if ti.done {
|
||||
return none
|
||||
}
|
||||
|
||||
// The first call to `next` will cause the sliding window to move to where
|
||||
// the requested offset starts
|
||||
if !ti.started {
|
||||
ti.advance_window()
|
||||
|
||||
// Skip all matched targets until the requested offset
|
||||
for !ti.done && ti.filtered_offset + u64(ti.window.len) <= ti.filter.offset {
|
||||
ti.filtered_offset += u64(ti.window.len)
|
||||
ti.advance_window()
|
||||
}
|
||||
|
||||
if ti.done {
|
||||
return none
|
||||
}
|
||||
|
||||
left_inside_window := ti.filter.offset - ti.filtered_offset
|
||||
ti.window_index = left_inside_window
|
||||
ti.filtered_offset += left_inside_window
|
||||
|
||||
ti.started = true
|
||||
}
|
||||
|
||||
return_value := ti.window[ti.window_index]
|
||||
|
||||
ti.window_index++
|
||||
ti.filtered_offset++
|
||||
|
||||
// Next call will be past the requested offset
|
||||
if ti.filter.limit > 0 && ti.filtered_offset == ti.filter.offset + ti.filter.limit {
|
||||
ti.done = true
|
||||
}
|
||||
|
||||
// Ensure the next call has a new valid window
|
||||
if ti.window_index == u64(ti.window.len) {
|
||||
ti.advance_window()
|
||||
ti.window_index = 0
|
||||
}
|
||||
|
||||
return return_value
|
||||
}
|
||||
|
||||
// collect consumes the entire iterator & returns the result as an array.
|
||||
pub fn (mut ti TargetsIterator) collect() []Target {
|
||||
mut out := []Target{}
|
||||
|
||||
for t in ti {
|
||||
out << t
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
Reference in a new issue