feat: 🎸 Add sqlite handler and parser (#2933)
* feat: 🎸 Add sqlite handler and parser ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17410
This commit is contained in:
1 parent
8abed1a809
commit
e0982da744
33 files changed
+1692
-111
No files matched your search
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { generateSQLExpressions } from 'api/utils/sqlite-query';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
const date = new Date(2025, 1, 1);
|
||||
const isoDateString = date.toISOString();
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
String.prototype.removeExtraWhiteSpace = function () {
|
||||
return this.replace(/^\s+/gm, '').trim();
|
||||
};
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
delete String.prototype.removeExtraWhiteSpace;
|
||||
});
|
||||
|
||||
// TODO: Add a normal LIKE search when we add a resource that uses it
|
||||
test('it generates search correctly with FTS5', function (assert) {
|
||||
const query = { search: 'favorite' };
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
WHERE rowid IN (SELECT rowid FROM target_fts WHERE target_fts MATCH ?)
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, ['"favorite"*']);
|
||||
});
|
||||
|
||||
test('it grabs resources not in the model mapping', function (assert) {
|
||||
const query = {
|
||||
filters: { id: [{ equals: 'tokenKey' }] },
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('token', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM token
|
||||
WHERE (id = ?)`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, ['tokenKey']);
|
||||
});
|
||||
|
||||
test('it executes count queries correctly', function (assert) {
|
||||
const select = {
|
||||
select: ['count(*) as total'],
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', {}, select);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT count(*) as total FROM target
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, []);
|
||||
});
|
||||
|
||||
test.each(
|
||||
'it generates filters correctly',
|
||||
{
|
||||
equals: {
|
||||
query: {
|
||||
filters: {
|
||||
type: [{ equals: 'ssh' }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause: 'WHERE (type = ?)',
|
||||
expectedParams: ['ssh'],
|
||||
},
|
||||
notEquals: {
|
||||
query: {
|
||||
filters: {
|
||||
type: [{ notEquals: 'ssh' }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause: 'WHERE (type != ?)',
|
||||
expectedParams: ['ssh'],
|
||||
},
|
||||
contains: {
|
||||
query: {
|
||||
filters: {
|
||||
type: [{ contains: 'ssh' }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause: 'WHERE (type LIKE ?)',
|
||||
expectedParams: ['%ssh%'],
|
||||
},
|
||||
greaterThan: {
|
||||
query: {
|
||||
filters: {
|
||||
created_time: [{ gt: date }],
|
||||
numberField: [{ gt: 10 }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause: 'WHERE (created_time > ?) AND (numberField > ?)',
|
||||
expectedParams: [isoDateString, 10],
|
||||
},
|
||||
lessThan: {
|
||||
query: {
|
||||
filters: {
|
||||
created_time: [{ lt: date }],
|
||||
numberField: [{ lt: 10 }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause: 'WHERE (created_time < ?) AND (numberField < ?)',
|
||||
expectedParams: [isoDateString, 10],
|
||||
},
|
||||
greaterThanOrEqual: {
|
||||
query: {
|
||||
filters: {
|
||||
created_time: [{ gte: date }],
|
||||
numberField: [{ gte: 10 }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause: 'WHERE (created_time >= ?) AND (numberField >= ?)',
|
||||
expectedParams: [isoDateString, 10],
|
||||
},
|
||||
lessThanOrEqual: {
|
||||
query: {
|
||||
filters: {
|
||||
created_time: [{ lte: date }],
|
||||
numberField: [{ lte: 10 }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause: 'WHERE (created_time <= ?) AND (numberField <= ?)',
|
||||
expectedParams: [isoDateString, 10],
|
||||
},
|
||||
logicalOperators: {
|
||||
query: {
|
||||
filters: {
|
||||
id: {
|
||||
logicalOperator: 'and',
|
||||
values: [{ notEquals: 'id1' }, { notEquals: 'id2' }],
|
||||
},
|
||||
status: {
|
||||
logicalOperator: 'or',
|
||||
values: [{ equals: 'active' }, { equals: 'pending' }],
|
||||
},
|
||||
type: [{ equals: 'ssh' }],
|
||||
},
|
||||
},
|
||||
expectedWhereClause:
|
||||
'WHERE (id != ? AND id != ?) AND (status = ? OR status = ?) AND (type = ?)',
|
||||
expectedParams: ['id1', 'id2', 'active', 'pending', 'ssh'],
|
||||
},
|
||||
},
|
||||
|
||||
function (assert, { query, expectedWhereClause, expectedParams }) {
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
${expectedWhereClause}
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, expectedParams);
|
||||
},
|
||||
);
|
||||
|
||||
test.each(
|
||||
'it generates sort direction correctly',
|
||||
[
|
||||
['asc', 'ASC'],
|
||||
['desc', 'DESC'],
|
||||
],
|
||||
function (assert, [direction, expectedDirection]) {
|
||||
const query = {
|
||||
sort: { attribute: 'name', direction },
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
ORDER BY name ${expectedDirection}`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, []);
|
||||
},
|
||||
);
|
||||
|
||||
test.each(
|
||||
'it generates pagination clause',
|
||||
[
|
||||
{ page: 1, pageSize: 10, expectedParams: [10, 0] },
|
||||
{ page: 3, pageSize: 10, expectedParams: [10, 20] },
|
||||
{ page: 7, pageSize: 50, expectedParams: [50, 300] },
|
||||
],
|
||||
function (assert, { page, pageSize, expectedParams }) {
|
||||
const { sql, parameters } = generateSQLExpressions(
|
||||
'target',
|
||||
{},
|
||||
{ page, pageSize },
|
||||
);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
ORDER BY created_time DESC
|
||||
LIMIT ? OFFSET ?`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, expectedParams);
|
||||
},
|
||||
);
|
||||
|
||||
test('it generates FTS5 search with filters', function (assert) {
|
||||
const query = {
|
||||
search: 'favorite',
|
||||
filters: {
|
||||
id: {
|
||||
logicalOperator: 'and',
|
||||
values: [{ notEquals: 'id1' }, { notEquals: 'id2' }],
|
||||
},
|
||||
status: [{ equals: 'active' }, { equals: 'pending' }],
|
||||
},
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
WHERE (id != ? AND id != ?) AND (status = ? OR status = ?) AND rowid IN (SELECT rowid FROM target_fts WHERE target_fts MATCH ?)
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, [
|
||||
'id1',
|
||||
'id2',
|
||||
'active',
|
||||
'pending',
|
||||
'"favorite"*',
|
||||
]);
|
||||
});
|
||||
|
||||
test('it generates SQL with all clauses combined', function (assert) {
|
||||
const query = {
|
||||
search: 'favorite',
|
||||
filters: {
|
||||
type: [{ equals: 'ssh' }],
|
||||
status: {
|
||||
logicalOperator: 'or',
|
||||
values: [{ equals: 'active' }, { equals: 'pending' }],
|
||||
},
|
||||
created_time: [{ gte: date }],
|
||||
},
|
||||
sort: { attribute: 'name', direction: 'desc' },
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', query, {
|
||||
page: 2,
|
||||
pageSize: 15,
|
||||
select: ['data'],
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT data FROM target
|
||||
WHERE (type = ?) AND (status = ? OR status = ?) AND (created_time >= ?) AND rowid IN (SELECT rowid FROM target_fts WHERE target_fts MATCH ?)
|
||||
ORDER BY name DESC
|
||||
LIMIT ? OFFSET ?`.removeExtraWhiteSpace(),
|
||||
);
|
||||
|
||||
assert.deepEqual(parameters, [
|
||||
'ssh',
|
||||
'active',
|
||||
'pending',
|
||||
isoDateString,
|
||||
'"favorite"*',
|
||||
15,
|
||||
15,
|
||||
]);
|
||||
});
|
||||
|
||||
test.each(
|
||||
'it handles empty and invalid fields',
|
||||
{
|
||||
emptyObject: {},
|
||||
emptySearch: { search: '' },
|
||||
emptyFilter: { filters: {} },
|
||||
emptyFilterAttributes: { filters: { type: [] } },
|
||||
invalidSortAttribute: { attribute: 'test', direction: 'asc' },
|
||||
onlyPage: { page: 10 },
|
||||
onlyPageSize: { pageSize: 5 },
|
||||
},
|
||||
function (assert, query) {
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, []);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user