feat: 🎸 Add sqlite support to sessions (#2978)
* feat: 🎸 Add sqlite support to sessions * refactor: 💡 only peek sqlite db when batching queries ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17411
This commit is contained in:
1 parent
dc026c44a1
commit
4c7ea161cd
3 files changed
+144
-43
No files matched your search
@@ -99,6 +99,16 @@ export const modelMapping = {
|
||||
target_scope_name: 'create_time_values.target.scope.name',
|
||||
created_time: 'created_time',
|
||||
},
|
||||
session: {
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
status: 'status',
|
||||
endpoint: 'endpoint',
|
||||
target_id: 'target_id',
|
||||
user_id: 'user_id',
|
||||
scope_id: 'scope.scope_id',
|
||||
created_time: 'created_time',
|
||||
},
|
||||
};
|
||||
|
||||
// A list of tables that we support searching using FTS5 in SQLite.
|
||||
@@ -113,6 +123,7 @@ export const searchTables = new Set([
|
||||
'auth-method',
|
||||
'host-catalog',
|
||||
'session-recording',
|
||||
'session',
|
||||
]);
|
||||
|
||||
export default class SqliteDbService extends Service {
|
||||
|
||||
@@ -388,6 +388,45 @@ CREATE TRIGGER IF NOT EXISTS session_recording_ad AFTER DELETE ON session_record
|
||||
VALUES('delete', old.rowid, old.id, old.type, old.state, old.start_time, old.end_time, old.duration, old.scope_id, old.user_id, old.user_name, old.target_id, old.target_name, old.target_scope_id, old.target_scope_name, old.created_time);
|
||||
END;`;
|
||||
|
||||
const createSessionTables = `
|
||||
CREATE TABLE IF NOT EXISTS session (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
type TEXT,
|
||||
status TEXT,
|
||||
endpoint TEXT,
|
||||
target_id TEXT,
|
||||
user_id TEXT,
|
||||
scope_id TEXT NOT NULL,
|
||||
created_time TEXT NOT NULL,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_session_scope_id_created_time ON session(scope_id, created_time DESC);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS session_fts USING fts5(
|
||||
id,
|
||||
type,
|
||||
status,
|
||||
endpoint,
|
||||
target_id,
|
||||
user_id,
|
||||
scope_id,
|
||||
created_time,
|
||||
content='',
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS session_ai AFTER INSERT ON session BEGIN
|
||||
INSERT INTO session_fts(
|
||||
id, type, status, endpoint, target_id, user_id, scope_id, created_time
|
||||
) VALUES (
|
||||
new.id, new.type, new.status, new.endpoint, new.target_id, new.user_id, new.scope_id, new.created_time
|
||||
);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS session_ad AFTER DELETE ON session BEGIN
|
||||
INSERT INTO session_fts(session_fts, rowid, id, type, status, endpoint, target_id, user_id, scope_id, created_time)
|
||||
VALUES('delete', old.rowid, old.id, old.type, old.status, old.endpoint, old.target_id, old.user_id, old.scope_id, old.created_time);
|
||||
END;`;
|
||||
|
||||
export const CREATE_TABLES = (version) => `
|
||||
BEGIN;
|
||||
|
||||
@@ -408,16 +447,7 @@ ${createScopeTables}
|
||||
${createAuthMethodTables}
|
||||
${createHostCatalogTables}
|
||||
${createSessionRecordingTables}
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "group" (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT,
|
||||
description TEXT,
|
||||
scope_id TEXT NOT NULL,
|
||||
created_time TEXT NOT NULL,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_group_scope_id_created_time ON "group"(scope_id, created_time DESC);
|
||||
${createSessionTables}
|
||||
|
||||
COMMIT;`;
|
||||
|
||||
|
||||
@@ -7,6 +7,13 @@ import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import chunk from 'lodash/chunk';
|
||||
|
||||
// Maximum expression tree depth is 1000 so
|
||||
// we limit the number of expressions in a query.
|
||||
// See "Maximum Depth Of An Expression Tree" in
|
||||
// https://www.sqlite.org/limits.html
|
||||
const MAX_EXPR_NUM = 999;
|
||||
|
||||
export default class ScopesScopeSessionsIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -114,10 +121,11 @@ export default class ScopesScopeSessionsIndexRoute extends Route {
|
||||
const totalItems = sessions.meta?.totalItems;
|
||||
|
||||
const allSessions = await this.getAllSessions(scope_id);
|
||||
const [associatedUsers, associatedTargets] = await Promise.all([
|
||||
this.getAssociatedUsers(scope, allSessions),
|
||||
this.getAssociatedTargets(scope, allSessions),
|
||||
]);
|
||||
const associatedUsers = await this.getAssociatedUsers(scope, allSessions);
|
||||
const associatedTargets = await this.getAssociatedTargets(
|
||||
scope,
|
||||
allSessions,
|
||||
);
|
||||
|
||||
return {
|
||||
sessions,
|
||||
@@ -159,24 +167,45 @@ export default class ScopesScopeSessionsIndexRoute extends Route {
|
||||
this.can.can('list model', globalScope, { collection: 'users' }) &&
|
||||
this.can.can('list model', orgScope, { collection: 'users' })
|
||||
) {
|
||||
const uniqueSessionUserIds = new Set(
|
||||
allSessions
|
||||
.filter((session) => session.user_id)
|
||||
.map((session) => session.user_id),
|
||||
const users = await this.store.query(
|
||||
'user',
|
||||
{
|
||||
scope_id: 'global',
|
||||
recursive: true,
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
},
|
||||
{ pushToStore: false },
|
||||
);
|
||||
const filters = {
|
||||
id: { values: [] },
|
||||
};
|
||||
uniqueSessionUserIds.forEach((userId) => {
|
||||
filters.id.values.push({ equals: userId });
|
||||
});
|
||||
const associatedUsersQuery = {
|
||||
scope_id: 'global',
|
||||
recursive: true,
|
||||
query: { filters },
|
||||
};
|
||||
|
||||
return this.store.query('user', associatedUsersQuery);
|
||||
if (!users.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const uniqueSessionUserIds = [
|
||||
...new Set(
|
||||
allSessions
|
||||
.filter((session) => session.user_id)
|
||||
.map((session) => session.user_id),
|
||||
),
|
||||
];
|
||||
const chunkedUserIds = chunk(uniqueSessionUserIds, MAX_EXPR_NUM);
|
||||
const associatedUsersPromises = chunkedUserIds.map((userIds) =>
|
||||
this.store.query(
|
||||
'user',
|
||||
{
|
||||
scope_id: 'global',
|
||||
query: {
|
||||
filters: {
|
||||
id: { values: userIds.map((userId) => ({ equals: userId })) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{ peekDb: true },
|
||||
),
|
||||
);
|
||||
const usersArray = await Promise.all(associatedUsersPromises);
|
||||
return usersArray.flat();
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -189,21 +218,52 @@ export default class ScopesScopeSessionsIndexRoute extends Route {
|
||||
*/
|
||||
async getAssociatedTargets(scope, allSessions) {
|
||||
if (this.can.can('list model', scope, { collection: 'targets' })) {
|
||||
const uniqueSessionTargetIds = new Set(
|
||||
allSessions
|
||||
.filter((session) => session.target_id)
|
||||
.map((session) => session.target_id),
|
||||
const targets = await this.store.query(
|
||||
'target',
|
||||
{
|
||||
scope_id: scope.id,
|
||||
query: {
|
||||
filters: {
|
||||
scope_id: [{ equals: scope.id }],
|
||||
},
|
||||
},
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
},
|
||||
{ pushToStore: false },
|
||||
);
|
||||
const filters = { id: { values: [] } };
|
||||
uniqueSessionTargetIds.forEach((targetId) => {
|
||||
filters.id.values.push({ equals: targetId });
|
||||
});
|
||||
const associatedTargetsQuery = {
|
||||
scope_id: scope.id,
|
||||
query: { filters },
|
||||
};
|
||||
|
||||
return this.store.query('target', associatedTargetsQuery);
|
||||
if (!targets.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const uniqueSessionTargetIds = [
|
||||
...new Set(
|
||||
allSessions
|
||||
.filter((session) => session.target_id)
|
||||
.map((session) => session.target_id),
|
||||
),
|
||||
];
|
||||
|
||||
const chunkedTargetIds = chunk(uniqueSessionTargetIds, MAX_EXPR_NUM);
|
||||
const associatedTargetsPromises = chunkedTargetIds.map((targetIds) =>
|
||||
this.store.query(
|
||||
'target',
|
||||
{
|
||||
scope_id: scope.id,
|
||||
query: {
|
||||
filters: {
|
||||
id: {
|
||||
values: targetIds.map((targetId) => ({ equals: targetId })),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ peekDb: true },
|
||||
),
|
||||
);
|
||||
const targetsArray = await Promise.all(associatedTargetsPromises);
|
||||
return targetsArray.flat();
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
Reference in new issue
Block a user