Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
bwinf
medal
Commits
3e18d6a7
Commit
3e18d6a7
authored
Feb 11, 2020
by
Robert Czechowski
Browse files
Add script to generate db connection sources from base source
parent
e8c96464
Pipeline
#389
passed with stage
in 6 minutes and 59 seconds
Changes
7
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
src/db_conn.base.rs
0 → 100644
View file @
3e18d6a7
impl
MedalObject
<
Connection
>
for
Participation
{
fn
save
(
&
mut
self
,
conn
:
&
Connection
)
{
let
query
=
"INSERT INTO participation (contest, session, start_date)
VALUES ($1, $2, $3)"
;
conn
.execute
(
query
,
&
[
&
self
.contest
,
&
self
.user
,
&
self
.start
])
.unwrap
();
}
}
impl
MedalObject
<
Connection
>
for
Group
{
fn
save
(
&
mut
self
,
conn
:
&
Connection
)
{
match
self
.get_id
()
{
Some
(
_id
)
=>
unimplemented!
(),
None
=>
{
let
query
=
"INSERT INTO usergroup (name, groupcode, tag, admin)
VALUES ($1, $2, $3, $4)"
;
conn
.execute
(
query
,
&
[
&
self
.name
,
&
self
.groupcode
,
&
self
.tag
,
&
self
.admin
])
.unwrap
();
self
.set_id
(
conn
.get_last_id
()
.unwrap
());
}
}
}
}
impl
MedalObject
<
Connection
>
for
Task
{
fn
save
(
&
mut
self
,
conn
:
&
Connection
)
{
let
query
=
"SELECT id
FROM task
WHERE taskgroup = $1
AND location = $2"
;
conn
.query_map_one
(
query
,
&
[
&
self
.taskgroup
,
&
self
.location
],
|
row
|
row
.get
(
0
))
.unwrap_or
(
None
)
.and_then
(|
id
|
{
self
.set_id
(
id
);
Some
(())
})
.unwrap_or
(());
// Err means no entry yet and is expected result
let
id
=
match
self
.get_id
()
{
Some
(
id
)
=>
{
let
query
=
"UPDATE task
SET taskgroup = $1, location = $2, stars = $3
WHERE id = $4"
;
conn
.execute
(
query
,
&
[
&
self
.taskgroup
,
&
self
.location
,
&
self
.stars
,
&
id
])
.unwrap
();
id
}
None
=>
{
let
query
=
"INSERT INTO task (taskgroup, location, stars)
VALUES ($1, $2, $3)"
;
conn
.execute
(
query
,
&
[
&
self
.taskgroup
,
&
self
.location
,
&
self
.stars
])
.unwrap
();
conn
.get_last_id
()
.unwrap
()
}
};
self
.set_id
(
id
);
}
}
impl
MedalObject
<
Connection
>
for
Taskgroup
{
fn
save
(
&
mut
self
,
conn
:
&
Connection
)
{
if
let
Some
(
first_task
)
=
self
.tasks
.get
(
0
)
{
let
query
=
"SELECT taskgroup.id
FROM taskgroup
JOIN task
ON task.taskgroup = taskgroup.id
WHERE contest = $1
AND task.location = $2"
;
conn
.query_map_one
(
query
,
&
[
&
self
.contest
,
&
first_task
.location
],
|
row
|
row
.get
(
0
))
.unwrap_or
(
None
)
.and_then
(|
id
|
{
self
.set_id
(
id
);
Some
(())
})
.unwrap_or
(());
// Err means no entry yet and is expected result
}
let
id
=
match
self
.get_id
()
{
Some
(
id
)
=>
{
let
query
=
"UPDATE taskgroup
SET contest = $1, name = $2, positionalnumber = $3
WHERE id = $4"
;
conn
.execute
(
query
,
&
[
&
self
.contest
,
&
self
.name
,
&
self
.positionalnumber
,
&
id
])
.unwrap
();
id
}
None
=>
{
let
query
=
"INSERT INTO taskgroup (contest, name, positionalnumber)
VALUES ($1, $2, $3)"
;
conn
.execute
(
query
,
&
[
&
self
.contest
,
&
self
.name
,
&
self
.positionalnumber
])
.unwrap
();
conn
.get_last_id
()
.unwrap
()
}
};
self
.set_id
(
id
);
for
mut
task
in
&
mut
self
.tasks
{
task
.taskgroup
=
id
;
task
.save
(
conn
);
}
}
}
impl
MedalObject
<
Connection
>
for
Contest
{
fn
save
(
&
mut
self
,
conn
:
&
Connection
)
{
let
query
=
"SELECT id
FROM contest
WHERE location = $1
AND filename = $2"
;
conn
.query_map_one
(
query
,
&
[
&
self
.location
,
&
self
.filename
],
|
row
|
row
.get
(
0
))
.unwrap_or
(
None
)
.and_then
(|
id
|
{
self
.set_id
(
id
);
Some
(())
})
.unwrap_or
(());
// Err means no entry yet and is expected result
let
id
=
match
self
.get_id
()
{
Some
(
id
)
=>
{
let
query
=
"UPDATE contest
SET location = $1,filename = $2, name = $3, duration = $4, public = $5, start_date = $6, end_date = $7
WHERE id = $8"
;
conn
.execute
(
query
,
&
[
&
self
.location
,
&
self
.filename
,
&
self
.name
,
&
self
.duration
,
&
self
.public
,
&
self
.start
,
&
self
.end
,
&
id
])
.unwrap
();
id
}
None
=>
{
let
query
=
"INSERT INTO contest (location, filename, name, duration, public, start_date, end_date)
VALUES ($1, $2, $3, $4, $5, $6, $7)"
;
conn
.execute
(
query
,
&
[
&
self
.location
,
&
self
.filename
,
&
self
.name
,
&
self
.duration
,
&
self
.public
,
&
self
.start
,
&
self
.end
])
.unwrap
();
conn
.get_last_id
()
.unwrap
()
}
};
self
.set_id
(
id
);
for
mut
taskgroup
in
&
mut
self
.taskgroups
{
taskgroup
.contest
=
id
;
taskgroup
.save
(
conn
);
}
}
}
impl
MedalConnection
for
Connection
{
fn
dbtype
(
&
self
)
->
&
'static
str
{
"postgres"
}
fn
migration_already_applied
(
&
self
,
name
:
&
str
)
->
bool
{
let
create_string
=
"CREATE TABLE IF NOT EXISTS migrations (name TEXT PRIMARY KEY);"
;
self
.execute
(
create_string
,
&
[])
.unwrap
();
let
query
=
"SELECT name FROM migrations WHERE name = $1"
;
self
.exists
(
query
,
&
[
&
name
])
}
fn
apply_migration
(
&
mut
self
,
name
:
&
str
,
contents
:
&
str
)
{
print!
(
"Applying migration `{}` … "
,
name
);
let
tx
=
self
.transaction
()
.unwrap
();
tx
.batch_execute
(
&
contents
)
.unwrap
();
tx
.execute
(
"INSERT INTO migrations (name) VALUES ($1)"
,
&
[
&
name
])
.unwrap
();
tx
.commit
()
.unwrap
();
println!
(
"OK."
);
}
// fn get_session<T: ToSql>(&self, key: T, keyname: &str) -> Option<SessionUser> {
fn
get_session
(
&
self
,
key
:
&
str
)
->
Option
<
SessionUser
>
{
let
query
=
"SELECT id, csrf_token, last_login, last_activity, permanent_login, username, password, logincode,
email, email_unconfirmed, email_confirmationcode, firstname, lastname, street, zip, city,
nation, grade, is_teacher, managed_by, oauth_provider, oauth_foreign_id, salt
FROM session
WHERE session_token = $1"
;
let
session
=
self
.query_map_one
(
query
,
&
[
&
key
],
|
row
|
SessionUser
{
id
:
row
.get
(
0
),
session_token
:
Some
(
key
.to_string
()),
csrf_token
:
row
.get
(
1
),
last_login
:
row
.get
(
2
),
last_activity
:
row
.get
(
3
),
permanent_login
:
row
.get
(
4
),
username
:
row
.get
(
5
),
password
:
row
.get
(
6
),
salt
:
row
.get
(
22
),
logincode
:
row
.get
(
7
),
email
:
row
.get
(
8
),
email_unconfirmed
:
row
.get
(
9
),
email_confirmationcode
:
row
.get
(
10
),
firstname
:
row
.get
(
11
),
lastname
:
row
.get
(
12
),
street
:
row
.get
(
13
),
zip
:
row
.get
(
14
),
city
:
row
.get
(
15
),
nation
:
row
.get
(
16
),
grade
:
row
.get
(
17
),
is_teacher
:
row
.get
(
18
),
managed_by
:
row
.get
(
19
),
oauth_provider
:
row
.get
(
20
),
oauth_foreign_id
:
row
.get
(
21
)
})
.ok
()
??
;
let
duration
=
if
session
.permanent_login
{
Duration
::
days
(
90
)
}
else
{
Duration
::
minutes
(
90
)
};
let
now
=
time
::
get_time
();
if
let
Some
(
last_activity
)
=
session
.last_activity
{
if
now
-
last_activity
<
duration
{
let
query
=
"UPDATE session
SET last_activity = $1
WHERE id = $2"
;
self
.execute
(
query
,
&
[
&
now
,
&
session
.id
])
.unwrap
();
return
Some
(
session
);
}
else
{
// Session timed out
// Should remove session token from session
return
None
;
}
}
// last_activity undefined
// TODO: What should happen here?
None
}
fn
save_session
(
&
self
,
session
:
SessionUser
)
{
self
.execute
(
"UPDATE session SET
username = $1,
password = $2,
salt = $3,
logincode = $4,
firstname = $5,
lastname = $6,
street = $7,
zip = $8,
city = $9,
grade = $10,
is_teacher = $11
WHERE id = $12"
,
&
[
&
session
.username
,
&
session
.password
,
&
session
.salt
,
&
session
.logincode
,
&
session
.firstname
,
&
session
.lastname
,
&
session
.street
,
&
session
.zip
,
&
session
.city
,
&
session
.grade
,
&
session
.is_teacher
,
&
session
.id
])
.unwrap
();
}
fn
new_session
(
&
self
,
session_token
:
&
str
)
->
SessionUser
{
let
csrf_token
=
helpers
::
make_csrf_token
();
let
now
=
time
::
get_time
();
let
query
=
"INSERT INTO session (session_token, csrf_token, last_activity, permanent_login, grade,
is_teacher)
VALUES ($1, $2, $3, $4, $5, $6)"
;
self
.execute
(
query
,
&
[
&
session_token
,
&
csrf_token
,
&
now
,
&
false
,
&
0
,
&
false
])
.unwrap
();
let
id
=
self
.get_last_id
()
.expect
(
"Expected to get last row id"
);
SessionUser
::
minimal
(
id
,
session_token
.to_owned
(),
csrf_token
)
}
fn
get_session_or_new
(
&
self
,
key
:
&
str
)
->
SessionUser
{
let
query
=
"UPDATE session
SET session_token = $1
WHERE session_token = $2"
;
self
.get_session
(
&
key
)
.ensure_alive
()
.unwrap_or_else
(||
{
// TODO: Factor this out in own function
// TODO: Should a new session key be generated every time?
self
.execute
(
query
,
&
[
&
Option
::
<
String
>
::
None
,
&
key
])
.unwrap
();
self
.new_session
(
&
key
)
})
}
fn
get_user_by_id
(
&
self
,
user_id
:
i32
)
->
Option
<
SessionUser
>
{
let
query
=
"SELECT session_token, csrf_token, last_login, last_activity, permanent_login, username, password,
logincode, email, email_unconfirmed, email_confirmationcode, firstname, lastname, street,
zip, city, nation, grade, is_teacher, managed_by, oauth_provider, oauth_foreign_id, salt
FROM session
WHERE id = $1"
;
self
.query_map_one
(
query
,
&
[
&
user_id
],
|
row
|
SessionUser
{
id
:
user_id
,
session_token
:
row
.get
(
0
),
csrf_token
:
row
.get
(
1
),
last_login
:
row
.get
(
2
),
last_activity
:
row
.get
(
3
),
permanent_login
:
row
.get
(
4
),
username
:
row
.get
(
5
),
password
:
row
.get
(
6
),
salt
:
row
.get
(
22
),
logincode
:
row
.get
(
7
),
email
:
row
.get
(
8
),
email_unconfirmed
:
row
.get
(
9
),
email_confirmationcode
:
row
.get
(
10
),
firstname
:
row
.get
(
11
),
lastname
:
row
.get
(
12
),
street
:
row
.get
(
13
),
zip
:
row
.get
(
14
),
city
:
row
.get
(
15
),
nation
:
row
.get
(
16
),
grade
:
row
.get
(
17
),
is_teacher
:
row
.get
(
18
),
managed_by
:
row
.get
(
19
),
oauth_provider
:
row
.get
(
20
),
oauth_foreign_id
:
row
.get
(
21
)
})
.ok
()
?
}
fn
get_user_and_group_by_id
(
&
self
,
user_id
:
i32
)
->
Option
<
(
SessionUser
,
Option
<
Group
>
)
>
{
let
session
=
self
.get_user_by_id
(
user_id
)
?
;
let
group_id
=
match
session
.managed_by
{
Some
(
id
)
=>
id
,
None
=>
return
Some
((
session
,
None
)),
};
let
query
=
"SELECT name, groupcode, tag, admin
FROM usergroup
WHERE id = $1"
;
let
res
=
self
.query_map_one
(
query
,
&
[
&
group_id
],
|
row
|
Group
{
id
:
Some
(
group_id
),
name
:
row
.get
(
0
),
groupcode
:
row
.get
(
1
),
tag
:
row
.get
(
2
),
admin
:
row
.get
(
3
),
members
:
Vec
::
new
()
})
.ok
()
?
;
match
res
{
Some
(
group
)
=>
Some
((
session
,
Some
(
group
))),
_
=>
Some
((
session
,
None
)),
}
}
//TODO: use session
fn
login
(
&
self
,
_session
:
Option
<&
str
>
,
username
:
&
str
,
password
:
&
str
)
->
Result
<
String
,
()
>
{
let
query
=
"SELECT id, password, salt
FROM session
WHERE username = $1"
;
self
.query_map_one
(
query
,
&
[
&
username
],
|
row
|
{
let
(
id
,
password_hash
,
salt
):
(
i32
,
Option
<
String
>
,
Option
<
String
>
)
=
(
row
.get
(
0
),
row
.get
(
1
),
row
.get
(
2
));
//password_hash ist das, was in der Datenbank steht
if
helpers
::
verify_password
(
&
password
,
&
salt
.expect
(
"salt from database empty"
),
&
password_hash
.expect
(
"password from database empty"
))
{
// TODO: fail more pleasantly
// Login okay, update session now!
let
session_token
=
helpers
::
make_session_token
();
let
csrf_token
=
helpers
::
make_csrf_token
();
let
now
=
time
::
get_time
();
let
query
=
"UPDATE session
SET session_token = $1, csrf_token = $2, last_login = $3, last_activity = $3
WHERE id = $4"
;
self
.execute
(
query
,
&
[
&
session_token
,
&
csrf_token
,
&
now
,
&
id
])
.unwrap
();
Ok
(
session_token
)
}
else
{
Err
(())
}
})
.map_err
(|
_
|
())
?
.ok_or
(())
?
}
//TODO: use session
fn
login_with_code
(
&
self
,
_session
:
Option
<&
str
>
,
logincode
:
&
str
)
->
Result
<
String
,
()
>
{
let
query
=
"SELECT id
FROM session
WHERE logincode = $1"
;
self
.query_map_one
(
query
,
&
[
&
logincode
],
|
row
|
{
// Login okay, update session now!
let
id
:
i32
=
row
.get
(
0
);
let
session_token
=
helpers
::
make_session_token
();
let
csrf_token
=
helpers
::
make_csrf_token
();
let
now
=
time
::
get_time
();
let
query
=
"UPDATE session
SET session_token = $1, csrf_token = $2, last_login = $3, last_activity = $3
WHERE id = $4"
;
self
.execute
(
query
,
&
[
&
session_token
,
&
csrf_token
,
&
now
,
&
id
])
.unwrap
();
session_token
})
.map_err
(|
_
|
())
?
.ok_or
(())
}
//TODO: use session
fn
login_foreign
(
&
self
,
_session
:
Option
<&
str
>
,
foreign_id
:
&
str
,
is_teacher
:
bool
,
firstname
:
&
str
,
lastname
:
&
str
)
->
Result
<
String
,
()
>
{
let
session_token
=
helpers
::
make_session_token
();
let
csrf_token
=
helpers
::
make_csrf_token
();
let
now
=
time
::
get_time
();
let
query
=
"SELECT id
FROM session
WHERE oauth_foreign_id = $1"
;
match
self
.query_map_one
(
query
,
&
[
&
foreign_id
],
|
row
|
->
i32
{
row
.get
(
0
)
})
{
Ok
(
Some
(
id
))
=>
{
let
query
=
"UPDATE session
SET session_token = $1, csrf_token = $2, last_login = $3, last_activity = $3
WHERE id = $4"
;
self
.execute
(
query
,
&
[
&
session_token
,
&
csrf_token
,
&
now
,
&
id
])
.unwrap
();
Ok
(
session_token
)
}
// Add!
_
=>
{
let
query
=
"INSERT INTO session (session_token, csrf_token, last_login, last_activity,
permanent_login, grade, is_teacher, oauth_foreign_id,
firstname, lastname)
VALUES ($1, $2, $3, $3, $4, $5, $6, $7, $8, $9)"
;
self
.execute
(
query
,
&
[
&
session_token
,
&
csrf_token
,
&
now
,
&
false
,
&
0
,
&
is_teacher
,
&
foreign_id
,
&
firstname
,
&
lastname
])
.unwrap
();
Ok
(
session_token
)
}
}
}
//TODO: use session
fn
create_user_with_groupcode
(
&
self
,
_session
:
Option
<&
str
>
,
groupcode
:
&
str
)
->
Result
<
String
,
()
>
{
let
query
=
"SELECT id
FROM usergroup
WHERE groupcode = $1"
;
let
group_id
=
self
.query_map_one
(
query
,
&
[
&
groupcode
],
|
row
|
->
i32
{
row
.get
(
0
)
})
.map_err
(|
_
|
())
?
.ok_or
(())
?
;
// Login okay, create session!
let
session_token
=
helpers
::
make_session_token
();
let
csrf_token
=
helpers
::
make_csrf_token
();
let
login_code
=
helpers
::
make_login_code
();
// TODO: check for collisions
let
now
=
time
::
get_time
();
let
query
=
"INSERT INTO session (session_token, csrf_token, last_login, last_activity, permanent_login,
logincode, grade, is_teacher, managed_by)
VALUES ($1, $2, $3, $3, $4, $5, $6, $7, $8)"
;
self
.execute
(
query
,
&
[
&
session_token
,
&
csrf_token
,
&
now
,
&
false
,
&
login_code
,
&
0
,
&
false
,
&
group_id
])
.unwrap
();
Ok
(
session_token
)
}
fn
create_group_with_users
(
&
self
,
mut
group
:
Group
)
{
// Generate group ID:
group
.save
(
self
);
for
user
in
group
.members
{
let
csrf_token
=
helpers
::
make_csrf_token
();
let
login_code
=
helpers
::
make_login_code
();
// TODO: check for collisions
let
query
=
"INSERT INTO session (firstname, lastname, csrf_token, permanent_login, logincode, grade,
is_teacher, managed_by)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)"
;
self
.execute
(
query
,
&
[
&
user
.firstname
,
&
user
.lastname
,
&
csrf_token
,
&
false
,
&
login_code
,
&
user
.grade
,
&
false
,
&
group
.id
])
.unwrap
();
}
}
fn
logout
(
&
self
,
session
:
&
str
)
{
let
query
=
"UPDATE session
SET session_token = NULL
WHERE session_token = $1"
;
self
.execute
(
query
,
&
[
&
session
])
.unwrap
();
}
fn
load_submission
(
&
self
,
session
:
&
SessionUser
,
task
:
i32
,
subtask
:
Option
<&
str
>
)
->
Option
<
Submission
>
{
match
subtask
{
None
=>
{
let
query
=
"SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation
FROM submission
WHERE task = $1
AND session = $2
ORDER BY id DESC
LIMIT 1"
;
self
.query_map_one
(
query
,
&
[
&
task
,
&
session
.id
],
|
row
|
Submission
{
id
:
Some
(
row
.get
(
0
)),
task
:
task
,
session_user
:
session
.id
,
grade
:
row
.get
(
1
),
validated
:
row
.get
(
2
),
nonvalidated_grade
:
row
.get
(
3
),
subtask_identifier
:
None
,
value
:
row
.get
(
4
),
date
:
row
.get
(
5
),
needs_validation
:
row
.get
(
6
)
})
.ok
()
?
}
Some
(
subtask_id
)
=>
{
let
query
=
"SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation
FROM submission
WHERE task = $1
AND session = $2
AND subtask_identifier = $3
ORDER BY id DESC
LIMIT 1"
;
self
.query_map_one
(
query
,
&
[
&
task
,
&
session
.id
,
&
subtask_id
],
|
row
|
{
Submission
{
id
:
Some
(
row
.get
(
0
)),
task
:
task
,
session_user
:
session
.id
,
grade
:
row
.get
(
1
),
validated
:
row
.get
(
2
),
nonvalidated_grade
:
row
.get
(
3
),
subtask_identifier
:
Some
(
subtask_id
.to_string
()),
value
:
row
.get
(
4
),
date
:
row
.get
(
5
),
needs_validation
:
row
.get
(
6
)
}
})
.ok
()
?
}
}
}
fn
submit_submission
(
&
self
,
mut
submission
:
Submission
)
{
submission
.save
(
self
);
let
mut
grade
=
self
.get_grade_by_submission
(
submission
.id
.unwrap
());
if
grade
.grade
.is_none
()
||
submission
.grade
>
grade
.grade
.unwrap
()
{
grade
.grade
=
Some
(
submission
.grade
);
grade
.validated
=
false
;
grade
.save
(
self
);
}
}
fn
get_grade_by_submission
(
&
self
,
submission_id
:
i32
)
->
Grade
{
let
query
=
"SELECT grade.taskgroup, grade.session, grade.grade, grade.validated
FROM grade
JOIN task ON grade.taskgroup = task.taskgroup
JOIN submission ON task.id = submission.task
AND grade.session = submission.session
WHERE submission.id = $1"
;
self
.query_map_one
(
query
,
&
[
&
submission_id
],
|
row
|
Grade
{
taskgroup
:
row
.get
(
0
),
user
:
row
.get
(
1
),
grade
:
row
.get
(
2
),
validated
:
row
.get
(
3
)
})
.unwrap_or
(
None
)
.unwrap_or_else
(||
{
let
query
=
"SELECT task.taskgroup, submission.session
FROM submission
JOIN task ON task.id = submission.task
WHERE submission.id = $1"
;
self
.query_map_one
(
query
,
&
[
&
submission_id
],
|
row
|
Grade
{
taskgroup
:
row
.get
(
0
),
user
:
row
.get
(
1
),
grade
:
None
,
validated
:
false
})
.unwrap
()
.unwrap
()
// should this unwrap?
})
}
fn
get_contest_groups_grades
(
&
self
,
session_id
:
i32
,
contest_id
:
i32
)
->
(
Vec
<
String
>
,
Vec
<
(
Group
,
Vec
<
(
UserInfo
,
Vec
<
Grade
>
)
>
)
>
)
{
let
query
=
"SELECT id, name
FROM taskgroup
WHERE contest = $1
ORDER BY id ASC"
;
let
tasknames
:
Vec
<
(
i32
,
String
)
>
=
self
.query_map_many
(
query
,
&
[
&
contest_id
],
|
row
|
(
row
.get
(
0
),
row
.get
(
1
)))
.unwrap
();
let
mut
taskindex
:
::
std
::
collections
::
BTreeMap
<
i32
,
usize
>
=
::
std
::
collections
::
BTreeMap
::
new
();
let
n_tasks
=
tasknames
.len
();
for
(
index
,
(
i
,
_
))
in
tasknames
.iter
()
.enumerate
()
{
taskindex
.insert
(
*
i
,
index
);
}
let
query
=
"SELECT grade.taskgroup, grade.session, grade.grade, grade.validated, usergroup.id, usergroup.name,
usergroup.groupcode, usergroup.tag, student.id, student.username, student.logincode,
student.firstname, student.lastname
FROM grade
JOIN taskgroup ON grade.taskgroup = taskgroup.id
JOIN session AS student ON grade.session = student.id
JOIN usergroup ON student.managed_by = usergroup.id