feat: 🎸 Add sqlite support for credential stores (#2967)

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17416
This commit is contained in:
Zhihe Li committed 2025-09-03 11:41:42 -04:00
1 parent 2154251c30
commit 9e612cf83d
13 files changed
+90 -11

No files matched your search

+15 -1
View File
@@ -49,10 +49,24 @@ export const modelMapping = {
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'credential-store': {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
};
// A list of tables that we support searching using FTS5 in SQLite.
export const searchTables = new Set(['target', 'alias', 'role', 'user']);
export const searchTables = new Set([
'target',
'alias',
'role',
'user',
'credential-store',
]);
export default class SqliteDbService extends Service {
// =attributes
+14 -6
View File
@@ -6,6 +6,7 @@
import { modelMapping } from 'api/services/sqlite';
import { searchTables } from 'api/services/sqlite';
import { typeOf } from '@ember/utils';
import { underscore } from '@ember/string';
/**
* Takes a POJO representing a filter query and builds a SQL query.
@@ -29,9 +30,10 @@ export function generateSQLExpressions(
let { search, filters, sort = {} } = query;
const conditions = [];
const parameters = [];
const tableName = underscore(resource);
addFilterConditions(filters, parameters, conditions);
addSearchConditions(search, resource, parameters, conditions);
addFilterConditions({ filters, parameters, conditions });
addSearchConditions({ search, resource, tableName, parameters, conditions });
const orderByClause = constructOrderByClause(resource, sort);
@@ -42,7 +44,7 @@ export function generateSQLExpressions(
parameters.push(pageSize, (page - 1) * pageSize);
}
const selectClause = `SELECT ${select ? select.join(', ') : '*'} FROM "${resource}"`;
const selectClause = `SELECT ${select ? select.join(', ') : '*'} FROM "${tableName}"`;
return {
// Replace any empty newlines or leading whitespace on each line to be consistent with formatting
@@ -58,7 +60,7 @@ export function generateSQLExpressions(
};
}
function addFilterConditions(filters, parameters, conditions) {
function addFilterConditions({ filters, parameters, conditions }) {
if (!filters) {
return;
}
@@ -105,7 +107,13 @@ function addFilterConditions(filters, parameters, conditions) {
}
}
function addSearchConditions(search, resource, parameters, conditions) {
function addSearchConditions({
search,
resource,
tableName,
parameters,
conditions,
}) {
if (!search) {
return;
}
@@ -117,7 +125,7 @@ function addSearchConditions(search, resource, parameters, conditions) {
// much more efficient with FTS queries when using rowids or MATCH (or both).
// We could have also used a join here but a subquery is simpler.
conditions.push(
`rowid IN (SELECT rowid FROM ${resource}_fts WHERE ${resource}_fts MATCH ?)`,
`rowid IN (SELECT rowid FROM ${tableName}_fts WHERE ${tableName}_fts MATCH ?)`,
);
return;
}
+40 -2
View File
@@ -3,6 +3,8 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import { underscore } from '@ember/string';
export const CLEAR_DB = `
PRAGMA writable_schema = 1;
DELETE FROM sqlite_master;
@@ -158,6 +160,41 @@ CREATE TRIGGER IF NOT EXISTS user_ad AFTER DELETE ON user BEGIN
VALUES('delete', old.rowid, old.id, old.name, old.description, old.scope_id, old.created_time);
END;`;
const createCredentialStoreTables = `
CREATE TABLE IF NOT EXISTS credential_store (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
name TEXT,
description TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_credential_store_scope_id_created_time ON credential_store(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS credential_store_fts USING fts5(
id,
type,
name,
description,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS credential_store_ai AFTER INSERT ON credential_store BEGIN
INSERT INTO credential_store_fts(
id, type, name, description, scope_id, created_time
) VALUES (
new.id, new.type, new.name, new.description, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS credential_store_ad AFTER DELETE ON credential_store BEGIN
INSERT INTO credential_store_fts(credential_store_fts, rowid, id, type, name, description, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.scope_id, old.created_time);
END;`;
export const CREATE_TABLES = (version) => `
BEGIN;
@@ -172,6 +209,7 @@ ${createTargetTables}
${createAliasTables}
${createRoleTables}
${createUserTables}
${createCredentialStoreTables}
CREATE TABLE IF NOT EXISTS "group" (
id TEXT NOT NULL PRIMARY KEY,
@@ -199,8 +237,8 @@ export const INSERT_STATEMENTS = (resource, items, modelMapping) => {
const numColumns = Array(columns.length + 1);
const placeholders = `(${numColumns.fill('?').join(', ')})`;
return `REPLACE INTO "${resource}" (${columns.join(', ')}, data) VALUES ${items.map(() => placeholders).join(', ')};`;
return `REPLACE INTO "${underscore(resource)}" (${columns.join(', ')}, data) VALUES ${items.map(() => placeholders).join(', ')};`;
};
export const DELETE_STATEMENT = (resource, ids) =>
`DELETE FROM "${resource}" WHERE id IN (${ids.map(() => '?').join(',')})`;
`DELETE FROM "${underscore(resource)}" WHERE id IN (${ids.map(() => '?').join(',')})`;