agentlang
Version:
The easiest way to build the most reliable AI agents - enterprise-grade teams of AI agents that collaborate with each other and humans
865 lines • 31.7 kB
JavaScript
import { DataSource, TableForeignKey, } from 'typeorm';
import { logger } from '../../logger.js';
import { asTableReference, DefaultVectorDimension, modulesAsOrmSchema, OwnersSuffix, VectorSuffix, } from './dbutil.js';
import { DefaultAuthInfo } from '../authinfo.js';
import { canUserCreate, canUserDelete, canUserRead, canUserUpdate } from '../../modules/auth.js';
import { GlobalEnvironment } from '../../interpreter.js';
import { newInstanceAttributes, RbacPermissionFlag, } from '../../module.js';
import { isString } from '../../util.js';
import { DeletedFlagAttributeName, ForceReadPermFlag, isRuntimeMode_dev, isRuntimeMode_generate_migration, isRuntimeMode_init_schema, isRuntimeMode_migration, isRuntimeMode_undo_migration, PathAttributeName, UnauthorisedError, } from '../../defs.js';
import { saveMigration } from '../../modules/core.js';
import { getAppSpec } from '../../loader.js';
export let defaultDataSource;
export class DbContext {
constructor(resourceFqName, authInfo, activeEnv, txnId, inKernelMode, rbacRules) {
this.inKernelMode = false;
this.needAuthCheckFlag = true;
this.resourceFqName = resourceFqName;
this.authInfo = authInfo;
this.activeEnv = activeEnv;
this.txnId = txnId;
if (inKernelMode !== undefined) {
this.inKernelMode = inKernelMode;
}
this.rbacRules = rbacRules;
}
static getGlobalContext() {
if (DbContext.GlobalDbContext === undefined) {
DbContext.GlobalDbContext = new DbContext('', DefaultAuthInfo, GlobalEnvironment, undefined, true);
}
return DbContext.GlobalDbContext;
}
// Shallow clone
clone() {
return new DbContext(this.resourceFqName, this.authInfo, this.activeEnv, this.txnId, this.inKernelMode);
}
getUserId() {
return this.authInfo.userId;
}
isForDelete() {
return this.authInfo.readForDelete;
}
isForUpdate() {
return this.authInfo.readForUpdate;
}
setResourceFqNameFrom(inst) {
this.resourceFqName = inst.getFqName();
return this;
}
setNeedAuthCheck(flag) {
this.needAuthCheckFlag = flag;
return this;
}
switchAuthCheck(flag) {
const old = this.needAuthCheckFlag;
this.needAuthCheckFlag = flag;
return old;
}
isPermitted() {
return this.inKernelMode || !this.needAuthCheckFlag;
}
isInKernelMode() {
return this.inKernelMode;
}
forceReadPermission() {
return this.activeEnv.lookup(ForceReadPermFlag);
}
}
export function makeJoinOn(attrName, attrValue, opr = '=') {
return {
attributeName: attrName,
attributeValue: attrValue,
operator: opr,
};
}
function mkDbName() {
return process.env.AGENTLANG_DB_NAME || `db-${Date.now()}`;
}
function makePostgresDataSource(entities, config) {
const synchronize = isRuntimeMode_dev() || isRuntimeMode_init_schema();
//const runMigrations = isRuntimeMode_migration() || isRuntimeMode_undo_migration() || !synchronize;
return new DataSource({
type: 'postgres',
host: process.env.POSTGRES_HOST || (config === null || config === void 0 ? void 0 : config.host) || 'localhost',
port: getPostgressEnvPort() || (config === null || config === void 0 ? void 0 : config.port) || 5432,
username: process.env.POSTGRES_USER || (config === null || config === void 0 ? void 0 : config.username) || 'postgres',
password: process.env.POSTGRES_PASSWORD || (config === null || config === void 0 ? void 0 : config.password) || 'postgres',
database: process.env.POSTGRES_DB || (config === null || config === void 0 ? void 0 : config.dbname) || 'postgres',
synchronize: synchronize,
migrationsRun: false,
dropSchema: false,
entities: entities,
invalidWhereValuesBehavior: {
null: 'sql-null',
undefined: 'ignore',
},
});
}
function getPostgressEnvPort() {
const s = process.env.POSTGRES_PORT;
if (s) {
return Number(s);
}
else {
return undefined;
}
}
function makeSqliteDataSource(entities, config) {
const synchronize = isRuntimeMode_dev() || isRuntimeMode_init_schema();
//const runMigrations = isRuntimeMode_migration() || isRuntimeMode_undo_migration() || !synchronize;
return new DataSource({
type: 'sqlite',
database: (config === null || config === void 0 ? void 0 : config.dbname) || mkDbName(),
synchronize: synchronize,
entities: entities,
migrationsRun: false,
dropSchema: false,
invalidWhereValuesBehavior: {
null: 'sql-null',
undefined: 'ignore',
},
});
}
async function execMigrationSql(dataSource, sql) {
const queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
for (let i = 0; i < sql.length; ++i) {
await queryRunner.query(sql[i]);
}
await queryRunner.commitTransaction();
}
async function maybeHandleMigrations(dataSource) {
const is_migration = isRuntimeMode_migration();
const is_undo_migration = isRuntimeMode_undo_migration();
const is_gen_migration = isRuntimeMode_generate_migration();
if (is_migration || is_undo_migration || is_gen_migration) {
const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
let ups;
if (is_migration || is_gen_migration) {
ups = new Array();
sqlInMemory.upQueries.forEach(upQuery => {
ups === null || ups === void 0 ? void 0 : ups.push(upQuery.query.replaceAll('`', '\\`'));
});
}
let downs;
if (is_undo_migration || is_gen_migration) {
downs = new Array();
sqlInMemory.downQueries.forEach(downQuery => {
downs === null || downs === void 0 ? void 0 : downs.push(downQuery.query.replaceAll('`', '\\`'));
});
}
if (is_migration && (ups === null || ups === void 0 ? void 0 : ups.length)) {
await saveMigration(getAppSpec().version, ups, downs);
await execMigrationSql(dataSource, ups);
}
else if (is_undo_migration && (downs === null || downs === void 0 ? void 0 : downs.length)) {
await saveMigration(getAppSpec().version, ups, downs);
await execMigrationSql(dataSource, downs);
}
else if (is_gen_migration) {
await saveMigration(getAppSpec().version, ups, downs);
}
}
}
function isBrowser() {
// window for DOM pages, self+importScripts for web workers
return ((typeof window !== 'undefined' && typeof window.document !== 'undefined') ||
(typeof self !== 'undefined' && typeof self.importScripts === 'function'));
}
function defaultLocateFile(file) {
// Out-of-the-box: use the official CDN in browsers.
if (isBrowser()) {
return `https://sql.js.org/dist/${file}`;
}
// Node: resolve from node_modules/sql.js/dist
try {
/* eslint-disable-next-line @typescript-eslint/no-require-imports */
const path = require('path');
const base = require.resolve('sql.js/dist/sql-wasm.js');
return path.join(path.dirname(base), file);
}
catch (_a) {
return file;
}
}
function makeSqljsDataSource(entities, _config, synchronize = true) {
return new DataSource({
type: 'sqljs',
autoSave: false,
sqlJsConfig: {
locateFile: defaultLocateFile,
},
synchronize: synchronize,
entities: entities,
});
}
const DbType = 'sqlite';
function getDbType(config) {
if (config === null || config === void 0 ? void 0 : config.type)
return config.type;
let envType;
try {
if (typeof process !== 'undefined' && process.env) {
envType = process.env.AL_DB_TYPE;
}
}
catch (_a) { }
if (envType)
return envType;
if (isBrowser())
return 'sqljs';
return DbType;
}
function getDsFunction(config) {
switch (getDbType(config)) {
case 'sqlite':
return makeSqliteDataSource;
case 'postgres':
return makePostgresDataSource;
case 'sqljs':
return makeSqljsDataSource;
default:
throw new Error(`Unsupported database type - ${config === null || config === void 0 ? void 0 : config.type}`);
}
}
export function isUsingSqlite() {
return getDbType() == 'sqlite';
}
export function isUsingSqljs() {
return getDbType() == 'sqljs';
}
export function isVectorStoreSupported() {
// Only Postgres supports pgvector
return getDbType() === 'postgres';
}
export async function initDatabase(config) {
if (defaultDataSource === undefined) {
const mkds = getDsFunction(config);
if (mkds) {
const ormScm = modulesAsOrmSchema();
defaultDataSource = mkds(ormScm.entities, config);
await defaultDataSource.initialize();
await maybeHandleMigrations(defaultDataSource);
if (ormScm.fkSpecs.length > 0) {
const qr = defaultDataSource.createQueryRunner();
for (let i = 0; i < ormScm.fkSpecs.length; ++i) {
const fk = ormScm.fkSpecs[i];
const fkobj = new TableForeignKey({
columnNames: [fk.columnName],
referencedColumnNames: [fk.targetColumnName],
referencedTableName: asTableReference(fk.targetModuleName, fk.targetEntityName),
onDelete: fk.onDelete,
onUpdate: fk.onUpdate,
});
try {
await qr.createForeignKey(asTableReference(fk.moduleName, fk.entityName), fkobj);
}
catch (reason) {
logger.warn(`initDatabase: ${reason}`);
}
}
}
const vectEnts = ormScm.vectorEntities.map((es) => {
return es.options.name;
});
if (vectEnts.length > 0) {
await initVectorStore(vectEnts, DbContext.getGlobalContext());
}
}
else {
throw new Error(`Unsupported database type - ${DbType}`);
}
}
}
export async function resetDefaultDatabase() {
if (defaultDataSource && defaultDataSource.isInitialized) {
await defaultDataSource.destroy();
defaultDataSource = undefined;
}
}
function ownersTable(tableName) {
return (tableName.replace('.', '_') + OwnersSuffix).toLowerCase();
}
async function insertRowsHelper(tableName, rows, ctx, doUpsert) {
const repo = getDatasourceForTransaction(ctx.txnId).getRepository(tableName);
if (doUpsert)
await repo.save(rows);
else
await repo.insert(rows);
}
export async function addRowForFullTextSearch(tableName, id, vect, ctx) {
if (!isVectorStoreSupported())
return;
try {
const vecTableName = tableName + VectorSuffix;
const qb = getDatasourceForTransaction(ctx.txnId).createQueryBuilder();
const { default: pgvector } = await import('pgvector');
await qb
.insert()
.into(vecTableName)
.values([{ id: id, embedding: pgvector.toSql(vect) }])
.execute();
}
catch (err) {
logger.error(`Failed to add row to vector store - ${err}`);
}
}
export async function initVectorStore(tableNames, ctx) {
if (!isVectorStoreSupported()) {
logger.info(`Vector store not supported for ${getDbType()}, skipping init...`);
return;
}
let notInited = true;
tableNames.forEach(async (vecTableName) => {
const vecRepo = getDatasourceForTransaction(ctx.txnId).getRepository(vecTableName);
if (notInited) {
let failure = false;
try {
await vecRepo.query('CREATE EXTENSION IF NOT EXISTS vector');
}
catch (err) {
logger.error(`Failed to initialize vector store - ${err}`);
failure = true;
}
if (failure)
return;
notInited = false;
}
await vecRepo.query(`CREATE TABLE IF NOT EXISTS ${vecTableName} (
id varchar PRIMARY KEY,
embedding vector(${DefaultVectorDimension}),
__is_deleted__ boolean default false
)`);
});
}
export async function vectorStoreSearch(tableName, searchVec, limit, ctx) {
if (!isVectorStoreSupported()) {
// Not supported on sqljs/sqlite
return [];
}
try {
let hasGlobalPerms = ctx.isPermitted();
if (!hasGlobalPerms) {
const userId = ctx.getUserId();
const fqName = ctx.resourceFqName;
const env = ctx.activeEnv;
hasGlobalPerms = await canUserRead(userId, fqName, env);
}
const vecTableName = tableName + VectorSuffix;
const qb = getDatasourceForTransaction(ctx.txnId).getRepository(tableName).manager;
const { default: pgvector } = await import('pgvector');
let ownersJoinCond = '';
if (!hasGlobalPerms) {
const ot = ownersTable(tableName);
ownersJoinCond = `inner join ${ot} on
${ot}.path = ${vecTableName}.id and ${ot}.user_id = '${ctx.authInfo.userId}' and ${ot}.r = true`;
}
const sql = `select ${vecTableName}.id from ${vecTableName} ${ownersJoinCond} order by embedding <-> $1 LIMIT ${limit}`;
return await qb.query(sql, [pgvector.toSql(searchVec)]);
}
catch (err) {
logger.error(`Vector store search failed - ${err}`);
return [];
}
}
export async function vectorStoreSearchEntryExists(tableName, id, ctx) {
if (!isVectorStoreSupported())
return false;
try {
const qb = getDatasourceForTransaction(ctx.txnId).getRepository(tableName).manager;
const vecTableName = tableName + VectorSuffix;
const result = await qb.query(`select id from ${vecTableName} where id = $1`, [id]);
return result !== null && result.length > 0;
}
catch (err) {
logger.error(`Vector store search failed - ${err}`);
}
return false;
}
export async function deleteFullTextSearchEntry(tableName, id, ctx) {
if (!isVectorStoreSupported())
return;
try {
const qb = getDatasourceForTransaction(ctx.txnId).getRepository(tableName).manager;
const vecTableName = tableName + VectorSuffix;
await qb.query(`delete from ${vecTableName} where id = $1`, [id]);
}
catch (err) {
logger.error(`Vector store delete failed - ${err}`);
}
}
async function checkUserPerm(opr, ctx, instRows) {
let hasPerm = ctx.isPermitted();
if (!hasPerm) {
const userId = ctx.getUserId();
let f;
switch (opr) {
case RbacPermissionFlag.CREATE:
f = canUserCreate;
break;
case RbacPermissionFlag.READ:
f = canUserRead;
break;
case RbacPermissionFlag.UPDATE:
f = canUserUpdate;
break;
case RbacPermissionFlag.DELETE:
f = canUserDelete;
break;
default:
f = undefined;
}
if (f !== undefined) {
hasPerm = await f(userId, ctx.resourceFqName, ctx.activeEnv);
}
}
if (!hasPerm) {
hasPerm = await isOwnerOfParent(instRows[PathKey], ctx);
}
return hasPerm;
}
async function checkCreatePermission(ctx, inst) {
const tmpCtx = ctx.clone().setResourceFqNameFrom(inst);
return await checkUserPerm(RbacPermissionFlag.CREATE, tmpCtx, Object.fromEntries(inst.attributes));
}
export async function insertRows(tableName, rows, ctx, doUpsert = false) {
let hasPerm = ctx.isPermitted();
if (!hasPerm) {
hasPerm = await checkUserPerm(RbacPermissionFlag.CREATE, ctx, rows[0]);
}
if (hasPerm) {
await insertRowsHelper(tableName, rows, ctx, doUpsert);
if (!doUpsert) {
if (!ctx.isInKernelMode()) {
await createOwnership(tableName, rows, ctx);
}
else if (ctx.forceReadPermission()) {
await createReadPermission(tableName, rows, ctx);
}
if (ctx.rbacRules) {
for (let i = 0; i < ctx.rbacRules.length; ++i) {
const rbacRule = ctx.rbacRules[i];
const e = rbacRule.expression;
if (e) {
const [selfRef, userRef] = e.lhs.startsWith('this.') ? [e.lhs, e.rhs] : [e.rhs, e.lhs];
if (userRef == 'auth.user') {
const attr = selfRef.split('.')[1];
for (let j = 0; j < rows.length; ++j) {
const r = rows[j];
const userId = r[attr];
if (userId) {
await createLimitedOwnership(tableName, [r], userId, rbacRule.permissions, ctx);
}
}
}
}
}
}
}
}
else {
throw new UnauthorisedError({ opr: 'insert', entity: tableName });
}
}
export async function insertRow(tableName, row, ctx, doUpsert) {
const rows = new Array();
rows.push(row);
await insertRows(tableName, rows, ctx, doUpsert);
}
export async function insertBetweenRow(n, a1, a2, node1, node2, relEntry, ctx) {
let hasPerm = await checkCreatePermission(ctx, node1);
if (hasPerm) {
hasPerm = await checkCreatePermission(ctx, node2);
}
if (hasPerm) {
const attrs = newInstanceAttributes();
const p1 = node1.attributes.get(PathAttributeName);
const p2 = node2.attributes.get(PathAttributeName);
attrs.set(a1, p1);
attrs.set(a2, p2);
attrs.set(PathAttributeName, crypto.randomUUID());
if (relEntry.isOneToMany()) {
attrs.set(relEntry.joinNodesAttributeName(), `${p1}_${p2}`);
}
const row = Object.fromEntries(attrs);
await insertRow(n, row, ctx.clone().setNeedAuthCheck(false), false);
}
else {
throw new UnauthorisedError({ opr: 'insert', entity: n });
}
}
const PathKey = PathAttributeName;
const AllPerms = new Set()
.add(RbacPermissionFlag.CREATE)
.add(RbacPermissionFlag.READ)
.add(RbacPermissionFlag.UPDATE)
.add(RbacPermissionFlag.DELETE);
async function createOwnership(tableName, rows, ctx) {
await createLimitedOwnership(tableName, rows, ctx.authInfo.userId, AllPerms, ctx);
}
const ReadPermOnly = new Set().add(RbacPermissionFlag.READ);
async function createReadPermission(tableName, rows, ctx) {
await createLimitedOwnership(tableName, rows, ctx.authInfo.userId, ReadPermOnly, ctx);
}
async function createLimitedOwnership(tableName, rows, userId, perms, ctx) {
const ownerRows = [];
rows.forEach((r) => {
ownerRows.push({
id: crypto.randomUUID(),
path: r[PathKey],
user_id: userId,
c: perms.has(RbacPermissionFlag.CREATE),
r: perms.has(RbacPermissionFlag.READ),
d: perms.has(RbacPermissionFlag.DELETE),
u: perms.has(RbacPermissionFlag.UPDATE),
});
});
const tname = ownersTable(tableName);
await insertRowsHelper(tname, ownerRows, ctx, false);
}
async function isOwnerOfParent(path, ctx) {
const parts = path.split('/');
if (parts.length <= 2) {
return false;
}
const parentPaths = new Array();
let i = 0;
let lastPath;
while (i < parts.length - 2) {
const parentName = parts[i].replace('$', '_');
const parentPath = `${lastPath ? lastPath + '/' : ''}${parts[i]}/${parts[i + 1]}`;
lastPath = `${parentPath}/${parts[i + 2]}`;
parentPaths.push([parentName, parentPath]);
i += 3;
}
if (parentPaths.length == 0) {
return false;
}
for (let i = 0; i < parentPaths.length; ++i) {
const [parentName, parentPath] = parentPaths[i];
const result = await isOwner(parentName, parentPath, ctx);
if (result)
return result;
}
return false;
}
async function isOwner(parentName, instPath, ctx) {
const userId = ctx.getUserId();
const tabName = ownersTable(parentName);
const alias = tabName;
const query = [
`${alias}.path = '${instPath}'`,
`${alias}.user_id = '${userId}'`,
`${alias}.type = 'o'`,
];
let result = undefined;
const sq = getDatasourceForTransaction(ctx.txnId)
.createQueryBuilder()
.select()
.from(tabName, alias)
.where(query.join(' AND '));
try {
await sq.getRawMany().then((r) => (result = r));
}
catch (reason) {
logger.error(`Failed to check ownership on parent ${parentName} - ${reason}`);
}
if (result === undefined || result.length === 0) {
return false;
}
return true;
}
export async function upsertRows(tableName, rows, ctx) {
await insertRows(tableName, rows, ctx, true);
}
export async function upsertRow(tableName, row, ctx) {
const rows = new Array();
rows.push(row);
await upsertRows(tableName, rows, ctx);
}
export async function updateRow(tableName, queryObj, queryVals, updateObj, ctx) {
await getDatasourceForTransaction(ctx.txnId)
.createQueryBuilder()
.update(tableName)
.set(updateObj)
.where(objectToWhereClause(queryObj, queryVals), queryVals)
.execute();
return true;
}
function queryObjectAsWhereClause(qobj) {
const ss = [];
qobj.forEach((kv) => {
const k = kv[0];
ss.push(`${k} = :${k}`);
});
return ss.join(' AND ');
}
export async function hardDeleteRow(tableName, queryObject, ctx) {
const clause = queryObjectAsWhereClause(queryObject);
await getDatasourceForTransaction(ctx.txnId)
.createQueryBuilder()
.delete()
.from(tableName)
.where(clause, Object.fromEntries(queryObject))
.execute();
return true;
}
function mkBetweenClause(tableName, k, queryVals) {
const ov = queryVals[k];
if (ov instanceof Array) {
const isstr = isString(ov[0]);
const v1 = isstr ? `'${ov[0]}'` : ov[0];
const v2 = isstr ? `'${ov[1]}'` : ov[1];
const s = tableName
? `"${tableName}"."${k}" BETWEEN ${v1} AND ${v2}`
: `"${k}" BETWEEN ${v1} AND ${v2}`;
delete queryVals[k];
return s;
}
else {
throw new Error(`between requires an array argument, not ${ov}`);
}
}
function objectToWhereClause(queryObj, queryVals, tableName) {
const clauses = new Array();
Object.entries(queryObj).forEach((value) => {
let op = value[1];
const k = value[0];
const isnullcheck = queryVals[k] === null;
if (isnullcheck) {
if (op === '=') {
op = 'IS';
}
else if (op === '<>' || op === '!=') {
op = 'IS NOT';
}
else {
throw new Error(`Operator ${op} cannot be appplied to SQL NULL`);
}
}
const v = isnullcheck ? 'NULL' : `:${k}`;
const clause = op == 'between'
? mkBetweenClause(tableName, k, queryVals)
: tableName
? `"${tableName}"."${k}" ${op} ${v}`
: `"${k}" ${op} ${v}`;
clauses.push(clause);
});
return clauses.join(' AND ');
}
function objectToRawWhereClause(queryObj, queryVals, tableName) {
const clauses = new Array();
Object.entries(queryObj).forEach((value) => {
let op = value[1];
const k = value[0];
if (queryVals[k] === null) {
if (op === '=') {
op = 'IS';
}
else if (op === '<>' || op === '!=') {
op = 'IS NOT';
}
else {
throw new Error(`Operator ${op} cannot be appplied to SQL NULL`);
}
}
let clause = '';
if (op == 'between') {
clause = mkBetweenClause(tableName, k, queryVals);
}
else {
const ov = queryVals[k];
const v = isString(ov) ? `'${ov}'` : ov;
clause = tableName ? `"${tableName}"."${k}" ${op} ${v}` : `"${k}" ${op} ${v}`;
}
clauses.push(clause);
});
if (clauses.length > 0) {
return clauses.join(' AND ');
}
else {
return '';
}
}
export async function getMany(tableName, queryObj, queryVals, distinct, ctx) {
const alias = tableName.toLowerCase();
const queryStr = withNotDeletedClause(alias, queryObj !== undefined ? objectToWhereClause(queryObj, queryVals, alias) : '');
let ownersJoinCond;
let ot = '';
let otAlias = '';
if (!ctx.isPermitted()) {
const userId = ctx.getUserId();
const fqName = ctx.resourceFqName;
const env = ctx.activeEnv;
let hasGlobalPerms = await canUserRead(userId, fqName, env);
if (hasGlobalPerms) {
if (ctx.isForUpdate()) {
hasGlobalPerms = await canUserUpdate(userId, fqName, env);
}
else if (ctx.isForDelete()) {
hasGlobalPerms = await canUserDelete(userId, fqName, env);
}
}
if (!hasGlobalPerms) {
ot = ownersTable(tableName);
otAlias = ot.toLowerCase();
ownersJoinCond = [
`${otAlias}.path = ${alias}.${PathAttributeName}`,
`${otAlias}.user_id = '${ctx.authInfo.userId}'`,
`${otAlias}.r = true`,
];
if (ctx.isForUpdate()) {
ownersJoinCond.push(`${otAlias}.u = true`);
}
if (ctx.isForDelete()) {
ownersJoinCond.push(`${otAlias}.d = true`);
}
}
}
const qb = getDatasourceForTransaction(ctx.txnId)
.getRepository(tableName)
.createQueryBuilder();
if (ownersJoinCond) {
qb.innerJoin(ot, otAlias, ownersJoinCond.join(' AND '));
}
if (distinct) {
qb.distinct(true);
}
qb.where(queryStr, queryVals);
return await qb.getMany();
}
export async function getManyByJoin(tableName, queryObj, queryVals, joinClauses, intoSpec, distinct, ctx) {
const alias = tableName.toLowerCase();
const queryStr = withNotDeletedClause(alias, queryObj !== undefined ? objectToRawWhereClause(queryObj, queryVals, alias) : '');
let ot = '';
let otAlias = '';
if (!ctx.isPermitted()) {
const userId = ctx.getUserId();
const fqName = ctx.resourceFqName;
const env = ctx.activeEnv;
const hasGlobalPerms = await canUserRead(userId, fqName, env);
if (!hasGlobalPerms) {
ot = ownersTable(tableName);
otAlias = ot.toLowerCase();
joinClauses.push({
tableName: otAlias,
joinOn: [
makeJoinOn(`${otAlias}.path`, `${alias}.${PathAttributeName}`),
makeJoinOn(`${otAlias}.user_id`, `'${ctx.authInfo.userId}'`),
makeJoinOn(`${otAlias}.r`, true),
],
});
}
}
const joinSql = new Array();
joinClauses.forEach((jc) => {
const joinType = jc.joinType ? jc.joinType : 'inner join';
joinSql.push(`${joinType} ${jc.tableName} as ${jc.tableName} on ${joinOnAsSql(jc.joinOn)} AND ${jc.tableName}.${DeletedFlagAttributeName} = false`);
if (jc.queryObject) {
const q = objectToRawWhereClause(jc.queryObject, jc.queryValues, jc.tableName);
if (q.length > 0) {
joinSql.push(` AND ${q}`);
}
}
});
const sql = `SELECT ${distinct ? 'DISTINCT' : ''} ${intoSpecToSql(intoSpec)} FROM ${tableName} ${joinSql.join('\n')} WHERE ${queryStr}`;
logger.debug(`Join Query: ${sql}`);
const qb = getDatasourceForTransaction(ctx.txnId).getRepository(tableName).manager;
return await qb.query(sql);
}
function intoSpecToSql(intoSpec) {
const cols = new Array();
intoSpec.forEach((v, k) => {
cols.push(`${v} AS "${k}"`);
});
return cols.join(', ');
}
function joinOnAsSql(joinOn) {
if (joinOn instanceof Array) {
return joinOn.map(joinOnAsSql).join(' AND ');
}
else {
return `${joinOn.attributeName} ${joinOn.operator} ${joinOn.attributeValue}`;
}
}
function notDeletedClause(alias) {
return `${alias}.${DeletedFlagAttributeName} = false`;
}
function withNotDeletedClause(alias, sql) {
if (sql == '') {
return notDeletedClause(alias);
}
else {
return `${sql} AND ${notDeletedClause(alias)}`;
}
}
function buildQueryFromConnnectionInfo(connAlias, mainAlias, connInfo) {
return `${connAlias}.${connInfo.fromColumn} = ${connInfo.fromValue} AND ${connAlias}.${connInfo.toColumn} = ${mainAlias}.${connInfo.toRef}`;
}
export async function getAllConnected(tableName, queryObj, queryVals, connInfo, ctx) {
const alias = tableName.toLowerCase();
const connAlias = connInfo.connectionTable.toLowerCase();
const qb = getDatasourceForTransaction(ctx.txnId)
.createQueryBuilder()
.select()
.from(tableName, alias)
.where(objectToWhereClause(queryObj, alias), queryVals)
.innerJoin(connInfo.connectionTable, connAlias, buildQueryFromConnnectionInfo(connAlias, alias, connInfo));
return await qb.getRawMany();
}
const transactionsDb = new Map();
export async function startDbTransaction() {
if (defaultDataSource !== undefined) {
const queryRunner = defaultDataSource.createQueryRunner();
await queryRunner.startTransaction();
const txnId = crypto.randomUUID();
transactionsDb.set(txnId, queryRunner);
return txnId;
}
else {
throw new Error('Database not initialized');
}
}
function getDatasourceForTransaction(txnId) {
if (txnId) {
const qr = transactionsDb.get(txnId);
if (qr === undefined) {
throw new Error(`Transaction not found - ${txnId}`);
}
else {
return qr.manager;
}
}
else {
if (defaultDataSource !== undefined)
return defaultDataSource;
else
throw new Error('No default datasource is initialized');
}
}
export async function commitDbTransaction(txnId) {
await endTransaction(txnId, true);
}
export async function rollbackDbTransaction(txnId) {
await endTransaction(txnId, false);
}
async function endTransaction(txnId, commit) {
const qr = transactionsDb.get(txnId);
if (qr && qr.isTransactionActive) {
try {
if (commit)
await qr.commitTransaction().catch((reason) => {
logger.error(`failed to commit transaction ${txnId} - ${reason}`);
});
else
await qr.rollbackTransaction().catch((reason) => {
logger.error(`failed to rollback transaction ${txnId} - ${reason}`);
});
}
finally {
await qr.release();
transactionsDb.delete(txnId);
}
}
}
//# sourceMappingURL=database.js.map