Browse Source

Merge pull request #7596 from nocodb/nc-feat/ttl-cache

feat: ttl cache
pull/7617/head
Mert E 9 months ago committed by GitHub
parent
commit
d195ea1482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 625
      packages/nocodb/src/cache/CacheMgr.ts
  2. 3
      packages/nocodb/src/cache/NocoCache.ts
  3. 412
      packages/nocodb/src/cache/RedisCacheMgr.ts
  4. 420
      packages/nocodb/src/cache/RedisMockCacheMgr.ts
  5. 1
      packages/nocodb/src/models/ApiToken.ts
  6. 2
      packages/nocodb/src/models/Base.ts
  7. 14
      packages/nocodb/src/models/Column.ts
  8. 3
      packages/nocodb/src/models/Filter.ts
  9. 2
      packages/nocodb/src/models/Hook.ts
  10. 2
      packages/nocodb/src/models/HookFilter.ts
  11. 4
      packages/nocodb/src/models/Model.ts
  12. 1
      packages/nocodb/src/models/ModelRoleVisibility.ts
  13. 2
      packages/nocodb/src/models/Sort.ts
  14. 3
      packages/nocodb/src/models/Source.ts
  15. 2
      packages/nocodb/src/models/User.ts
  16. 3
      packages/nocodb/src/models/View.ts
  17. 2
      packages/nocodb/src/utils/globals.ts

625
packages/nocodb/src/cache/CacheMgr.ts vendored

@ -1,36 +1,619 @@
import debug from 'debug';
import { Logger } from '@nestjs/common';
import type IORedis from 'ioredis';
import { CacheDelDirection, CacheGetType } from '~/utils/globals';
const log = debug('nc:cache');
const logger = new Logger('CacheMgr');
/*
- keys are stored as following:
- simple key: nc:<orgs>:<scope>:<model_id_1>
- value: { value: { ... }, parentKeys: [ "nc:<orgs>:<scope>:<model_id_1>:list" ], timestamp: 1234567890 }
- stored as stringified JSON
- list key: nc:<orgs>:<scope>:<model_id_1>:list
- stored as SET
- get returns `value` only
- getRaw returns the whole cache object with metadata
*/
const NC_REDIS_TTL = +process.env.NC_REDIS_TTL || 60 * 60 * 24 * 3; // 3 days
const NC_REDIS_GRACE_TTL = +process.env.NC_REDIS_GRACE_TTL || 60 * 60 * 24 * 1; // 1 day
export default abstract class CacheMgr { export default abstract class CacheMgr {
public abstract get(key: string, type: string): Promise<any>; client: IORedis;
public abstract set(key: string, value: any): Promise<any>; prefix: string;
public abstract setExpiring( context: string;
// avoid circular structure to JSON
getCircularReplacer = () => {
const seen = new WeakSet();
return (_, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
// @ts-ignore
async del(key: string[] | string): Promise<any> {
log(`${this.context}::del: deleting key ${key}`);
if (Array.isArray(key)) {
if (key.length) {
return this.client.del(key);
}
} else if (key) {
return this.client.del(key);
}
}
// @ts-ignore
private async getRaw(
key: string,
type?: string,
skipTTL = false,
): Promise<any> {
log(`${this.context}::getRaw: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key);
} else {
const res = await this.client.get(key);
if (res) {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
if (
o &&
Object.keys(o).length === 0 &&
Object.getPrototypeOf(o) === Object.prototype
) {
log(`${this.context}::get: object is empty!`);
}
if (!skipTTL && o.timestamp) {
const diff = Date.now() - o.timestamp;
if (diff > NC_REDIS_GRACE_TTL * 1000) {
await this.refreshTTL(key);
}
}
return Promise.resolve(o);
}
} catch (e) {
logger.error(`Bad value stored for key ${key} : ${res}`);
return Promise.resolve(res);
}
}
return Promise.resolve(res);
}
}
// @ts-ignore
async get(key: string, type: string): Promise<any> {
return this.getRaw(key, type).then((res) => {
if (res && res.value) {
return res.value;
}
return res;
});
}
// @ts-ignore
async set(
key: string,
value: any,
options: {
// when we prepare beforehand, we don't need to prepare again
skipPrepare?: boolean;
// timestamp for the value, if not provided, it will be set to current time
timestamp?: number;
} = {
skipPrepare: false,
},
): Promise<any> {
const { skipPrepare, timestamp } = options;
if (typeof value !== 'undefined' && value) {
log(`${this.context}::set: setting key ${key} with value ${value}`);
// if provided value is an array store it as a set
if (Array.isArray(value) && value.length) {
return new Promise((resolve) => {
this.client
.pipeline()
.sadd(key, value)
// - 60 seconds to avoid expiring list before any of its children
.expire(key, NC_REDIS_TTL - 60)
.exec((err) => {
if (err) {
logger.error(
`${this.context}::set: error setting key ${key} with value ${value}`,
);
}
resolve(true);
});
});
}
if (!skipPrepare) {
// try to get old key value
const keyValue = await this.getRaw(key);
// prepare new key value
value = this.prepareValue({
value,
parentKeys: this.getParents(keyValue),
timestamp,
});
}
return this.client
.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
'EX',
NC_REDIS_TTL,
)
.then(async () => {
await this.refreshTTL(key, timestamp);
return true;
});
} else {
log(`${this.context}::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async setExpiring(
key: string, key: string,
value: any, value: any,
seconds: number, seconds: number,
): Promise<any>; options: {
public abstract incrby(key: string, value: number): Promise<any>; // when we prepare beforehand, we don't need to prepare again
public abstract del(key: string[] | string): Promise<any>; skipPrepare?: boolean;
public abstract getList( // timestamp for the value, if not provided, it will be set to current time
timestamp?: number;
} = {
skipPrepare: false,
},
): Promise<any> {
const { skipPrepare, timestamp } = options;
if (typeof value !== 'undefined' && value) {
log(
`${this.context}::setExpiring: setting key ${key} with value ${value}`,
);
if (Array.isArray(value) && value.length) {
return new Promise((resolve) => {
this.client
.pipeline()
.sadd(key, value)
.expire(key, seconds)
.exec((err) => {
if (err) {
logger.error(
`${this.context}::set: error setting key ${key} with value ${value}`,
);
}
resolve(true);
});
});
}
if (!skipPrepare) {
// try to get old key value
const keyValue = await this.getRaw(key);
// prepare new key value
value = this.prepareValue({
value,
parentKeys: this.getParents(keyValue),
timestamp,
});
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
'EX',
seconds,
);
} else {
log(`${this.context}::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async incrby(key: string, value = 1): Promise<any> {
return this.client.incrby(key, value);
}
async getList(
scope: string, scope: string,
list: string[], subKeys: string[],
): Promise<{ ): Promise<{
list: any[]; list: any[];
isNoneList: boolean; isNoneList: boolean;
}>; }> {
public abstract setList( // remove null from arrays
subKeys = subKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const key =
subKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`;
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || [];
log(`${this.context}::getList: getting list with key ${key}`);
const isNoneList = arr.length && arr.includes('NONE');
if (isNoneList || !arr.length) {
return Promise.resolve({
list: [],
isNoneList,
});
}
log(`${this.context}::getList: getting list with keys ${arr}`);
const values = await this.client.mget(arr);
if (values.some((v) => v === null)) {
// FALLBACK: a key is missing from list, this should never happen
logger.error(`${this.context}::getList: missing value for ${key}`);
const allParents = [];
// get all parents from children
values.forEach((v) => {
if (v) {
try {
const o = JSON.parse(v);
if (typeof o === 'object') {
allParents.push(...this.getParents(o));
}
} catch (e) {
logger.error(
`${this.context}::getList: Bad value stored for key ${arr[0]} : ${v}`,
);
}
}
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return Promise.resolve({
list: [],
isNoneList,
});
}
if (values.length) {
try {
const o = JSON.parse(values[0]);
if (typeof o === 'object') {
const diff = Date.now() - o.timestamp;
if (diff > NC_REDIS_GRACE_TTL * 1000) {
await this.refreshTTL(key);
}
}
} catch (e) {
logger.error(
`${this.context}::getList: Bad value stored for key ${arr[0]} : ${values[0]}`,
);
}
}
return {
list: values.map((res) => {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
return o.value;
}
} catch (e) {
return res;
}
return res;
}),
isNoneList,
};
}
async setList(
scope: string, scope: string,
subListKeys: string[], subListKeys: string[],
list: any[], list: any[],
props?: string[], props: string[] = [],
): Promise<boolean>; ): Promise<boolean> {
public abstract deepDel( // remove null from arrays
scope: string, subListKeys = subListKeys.filter((k) => k);
key: string, // construct key for List
direction: string, // e.g. nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
): Promise<boolean>; const listKey =
public abstract appendToList( subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
if (!list.length) {
// Set NONE here so that it won't hit the DB on each page load
return this.set(listKey, ['NONE']);
}
// timestamp for list
const timestamp = Date.now();
// fetch existing list
const listOfGetKeys =
(await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
for (const o of list) {
// construct key for Get
let getKey = `${this.prefix}:${scope}:${o.id}`;
if (props.length) {
const propValues = props.map((p) => o[p]);
// e.g. nc:<orgs>:<scope>:<prop_value_1>:<prop_value_2>
getKey = `${this.prefix}:${scope}:${propValues.join(':')}`;
}
log(`${this.context}::setList: get key ${getKey}`);
// get key
let rawValue = await this.getRaw(getKey, CacheGetType.TYPE_OBJECT);
if (rawValue) {
log(`${this.context}::setList: preparing key ${getKey}`);
// prepare key
rawValue = this.prepareValue({
value: o,
parentKeys: this.getParents(rawValue),
newKey: listKey,
timestamp,
});
} else {
rawValue = this.prepareValue({
value: o,
parentKeys: [listKey],
timestamp,
});
}
// set key
log(`${this.context}::setList: setting key ${getKey}`);
await this.set(getKey, rawValue, {
skipPrepare: true,
timestamp,
});
// push key to list
listOfGetKeys.push(getKey);
}
// set list
log(`${this.context}::setList: setting list with key ${listKey}`);
return this.set(listKey, listOfGetKeys);
}
async deepDel(key: string, direction: string): Promise<boolean> {
log(`${this.context}::deepDel: choose direction ${direction}`);
if (direction === CacheDelDirection.CHILD_TO_PARENT) {
const childKey = await this.getRaw(key, CacheGetType.TYPE_OBJECT);
// given a child key, delete all keys in corresponding parent lists
const scopeList = this.getParents(childKey);
for (const listKey of scopeList) {
// get target list
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
if (!list.length) {
continue;
}
// remove target Key
list = list.filter((k) => k !== key);
// delete list
log(`${this.context}::deepDel: remove listKey ${listKey}`);
await this.del(listKey);
if (list.length) {
// set target list
log(`${this.context}::deepDel: set key ${listKey}`);
await this.set(listKey, list);
}
}
log(`${this.context}::deepDel: remove key ${key}`);
return await this.del(key);
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) {
key = /:list$/.test(key) ? key : `${key}:list`;
// given a list key, delete all the children
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY);
// delete each child key
await this.del(listOfChildren);
// delete list key
return await this.del(key);
} else {
log(`Invalid deepDel direction found : ${direction}`);
return Promise.resolve(false);
}
}
async appendToList(
scope: string, scope: string,
subListKeys: string[], subListKeys: string[],
key: string, key: string,
): Promise<boolean>; ): Promise<boolean> {
public abstract destroy(): Promise<boolean>; // remove null from arrays
public abstract export(): Promise<any>; subListKeys = subListKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
log(`${this.context}::appendToList: append key ${key} to ${listKey}`);
let list = await this.get(listKey, CacheGetType.TYPE_ARRAY);
if (!list || !list.length) {
return false;
}
if (list.includes('NONE')) {
list = [];
await this.del(listKey);
}
log(`${this.context}::appendToList: get key ${key}`);
// get Get Key
const rawValue = await this.getRaw(key, CacheGetType.TYPE_OBJECT);
log(`${this.context}::appendToList: preparing key ${key}`);
if (!rawValue) {
// FALLBACK: this is to get rid of all keys that would be effected by this (should never happen)
logger.error(`${this.context}::appendToList: value is empty for ${key}`);
const allParents = [];
// get all children
const listValues = await this.getList(scope, subListKeys);
// get all parents from children
listValues.list.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return false;
}
// prepare Get Key
const preparedValue = this.prepareValue({
value: rawValue.value ?? rawValue,
parentKeys: this.getParents(rawValue),
newKey: listKey,
});
// set Get Key
log(`${this.context}::appendToList: setting key ${key}`);
await this.set(key, preparedValue, {
skipPrepare: true,
});
list.push(key);
return this.set(listKey, list).then(async (res) => {
await this.refreshTTL(listKey);
return res;
});
}
// wrap value with metadata
prepareValue(args: {
value: any;
parentKeys: string[];
newKey?: string;
timestamp?: number;
}) {
const { value, parentKeys, newKey, timestamp } = args;
if (newKey && !parentKeys.includes(newKey)) {
parentKeys.push(newKey);
}
const cacheObj = {
value,
parentKeys,
timestamp: timestamp || Date.now(),
};
return cacheObj;
}
getParents(rawValue) {
if (rawValue && rawValue.parentKeys) {
return rawValue.parentKeys;
} else if (!rawValue) {
return [];
} else {
logger.error(
`${this.context}::getParents: parentKeys not found ${JSON.stringify(
rawValue,
)}`,
);
return [];
}
}
async refreshTTL(key: string, timestamp?: number): Promise<void> {
log(`${this.context}::refreshTTL: refreshing TTL for ${key}`);
const isParent = /:list$/.test(key);
timestamp = timestamp || Date.now();
if (isParent) {
const list =
(await this.getRaw(key, CacheGetType.TYPE_ARRAY, true)) || [];
if (list && list.length) {
const listValues = await this.client.mget(list);
const pipeline = this.client.pipeline();
for (const [i, v] of listValues.entries()) {
const key = list[i];
if (v) {
try {
const o = JSON.parse(v);
if (typeof o === 'object') {
if (o.timestamp !== timestamp) {
o.timestamp = timestamp;
pipeline.set(
key,
JSON.stringify(o, this.getCircularReplacer()),
'EX',
NC_REDIS_TTL,
);
}
}
} catch (e) {
logger.error(
`${this.context}::refreshTTL: Bad value stored for key ${key} : ${v}`,
);
}
}
}
pipeline.expire(key, NC_REDIS_TTL - 60);
await pipeline.exec();
}
} else {
const rawValue = await this.getRaw(key, null, true);
if (rawValue) {
if (rawValue.parentKeys && rawValue.parentKeys.length) {
for (const parent of rawValue.parentKeys) {
await this.refreshTTL(parent, timestamp);
}
} else {
if (rawValue.timestamp !== timestamp) {
rawValue.timestamp = timestamp;
await this.client.set(
key,
JSON.stringify(rawValue, this.getCircularReplacer()),
'EX',
NC_REDIS_TTL,
);
}
}
}
}
}
async destroy(): Promise<boolean> {
log('${this.context}::destroy: destroy redis');
return this.client.flushdb().then((r) => r === 'OK');
}
async export(): Promise<any> {
log('${this.context}::export: export data');
const data = await this.client.keys('*');
const res = {};
return await Promise.all(
data.map(async (k) => {
res[k] = await this.get(
k,
k.slice(-4) === 'list'
? CacheGetType.TYPE_ARRAY
: CacheGetType.TYPE_OBJECT,
);
}),
).then(() => {
return res;
});
}
} }

3
packages/nocodb/src/cache/NocoCache.ts vendored

@ -85,12 +85,11 @@ export default class NocoCache {
} }
public static async deepDel( public static async deepDel(
scope: string,
key: string, key: string,
direction: string, direction: string,
): Promise<boolean> { ): Promise<boolean> {
if (this.cacheDisabled) return Promise.resolve(true); if (this.cacheDisabled) return Promise.resolve(true);
return this.client.deepDel(scope, `${this.prefix}:${key}`, direction); return this.client.deepDel(`${this.prefix}:${key}`, direction);
} }
public static async appendToList( public static async appendToList(

412
packages/nocodb/src/cache/RedisCacheMgr.ts vendored

@ -1,18 +1,10 @@
import debug from 'debug'; import debug from 'debug';
import Redis from 'ioredis'; import Redis from 'ioredis';
import CacheMgr from './CacheMgr'; import CacheMgr from './CacheMgr';
import {
CacheDelDirection,
CacheGetType,
CacheListProp,
} from '~/utils/globals';
const log = debug('nc:cache'); const _log = debug('nc:cache');
export default class RedisCacheMgr extends CacheMgr { export default class RedisCacheMgr extends CacheMgr {
client: Redis;
prefix: string;
constructor(config: any) { constructor(config: any) {
super(); super();
this.client = new Redis(config); this.client = new Redis(config);
@ -29,406 +21,6 @@ export default class RedisCacheMgr extends CacheMgr {
// TODO(cache): fetch orgs once it's implemented // TODO(cache): fetch orgs once it's implemented
const orgs = 'noco'; const orgs = 'noco';
this.prefix = `nc:${orgs}`; this.prefix = `nc:${orgs}`;
} this.context = 'RedisCacheMgr';
// avoid circular structure to JSON
getCircularReplacer = () => {
const seen = new WeakSet();
return (_, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
// @ts-ignore
async del(key: string[] | string): Promise<any> {
log(`RedisCacheMgr::del: deleting key ${key}`);
if (Array.isArray(key)) {
if (key.length) {
return this.client.del(key);
}
} else if (key) {
return this.client.del(key);
}
}
// @ts-ignore
async get(key: string, type: string): Promise<any> {
log(`RedisCacheMgr::get: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key);
} else if (type === CacheGetType.TYPE_OBJECT) {
const res = await this.client.get(key);
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
if (
o &&
Object.keys(o).length === 0 &&
Object.getPrototypeOf(o) === Object.prototype
) {
log(`RedisCacheMgr::get: object is empty!`);
}
return Promise.resolve(o);
}
} catch (e) {
return Promise.resolve(res);
}
return Promise.resolve(res);
} else if (type === CacheGetType.TYPE_STRING) {
return await this.client.get(key);
}
log(`Invalid CacheGetType: ${type}`);
return Promise.resolve(false);
}
// @ts-ignore
async set(key: string, value: any): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(`RedisCacheMgr::set: setting key ${key} with value ${value}`);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
const keyValue = await this.get(key, CacheGetType.TYPE_OBJECT);
if (keyValue) {
value = await this.prepareValue(value, this.getParents(keyValue));
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
);
}
return this.client.set(key, value);
} else {
log(`RedisCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async setExpiring(key: string, value: any, seconds: number): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(
`RedisCacheMgr::setExpiring: setting key ${key} with value ${value} for ${seconds} seconds`,
);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
'EX',
seconds,
);
}
return this.client.set(key, value, 'EX', seconds);
} else {
log(`RedisCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async incrby(key: string, value = 1): Promise<any> {
return this.client.incrby(key, value);
}
async getList(
scope: string,
subKeys: string[],
): Promise<{
list: any[];
isNoneList: boolean;
}> {
// remove null from arrays
subKeys = subKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const key =
subKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`;
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || [];
log(`RedisCacheMgr::getList: getting list with key ${key}`);
const isNoneList = arr.length && arr.includes('NONE');
if (isNoneList || !arr.length) {
return Promise.resolve({
list: [],
isNoneList,
});
}
log(`RedisCacheMgr::getList: getting list with keys ${arr}`);
const values = await this.client.mget(arr);
if (values.some((v) => v === null)) {
// FALLBACK: a key is missing from list, this should never happen
console.error(`RedisCacheMgr::getList: missing value for ${key}`);
const allParents = [];
// get all parents from children
values.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return Promise.resolve({
list: [],
isNoneList,
});
}
return {
list: values.map((res) => {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
return o;
}
} catch (e) {
return res;
}
return res;
}),
isNoneList,
};
}
async setList(
scope: string,
subListKeys: string[],
list: any[],
props: string[] = [],
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// construct key for List
// e.g. nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
if (!list.length) {
// Set NONE here so that it won't hit the DB on each page load
return this.set(listKey, ['NONE']);
}
// fetch existing list
const listOfGetKeys =
(await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
for (const o of list) {
// construct key for Get
let getKey = `${this.prefix}:${scope}:${o.id}`;
if (props.length) {
const propValues = props.map((p) => o[p]);
// e.g. nc:<orgs>:<scope>:<prop_value_1>:<prop_value_2>
getKey = `${this.prefix}:${scope}:${propValues.join(':')}`;
}
log(`RedisCacheMgr::setList: get key ${getKey}`);
// get Get Key
let value = await this.get(getKey, CacheGetType.TYPE_OBJECT);
if (value) {
log(`RedisCacheMgr::setList: preparing key ${getKey}`);
// prepare Get Key
value = await this.prepareValue(o, this.getParents(value), listKey);
} else {
value = await this.prepareValue(o, [], listKey);
}
// set Get Key
log(`RedisCacheMgr::setList: setting key ${getKey}`);
await this.set(getKey, JSON.stringify(value, this.getCircularReplacer()));
// push Get Key to List
listOfGetKeys.push(getKey);
}
// set List Key
log(`RedisCacheMgr::setList: setting list with key ${listKey}`);
return this.set(listKey, listOfGetKeys);
}
async deepDel(
scope: string,
key: string,
direction: string,
): Promise<boolean> {
log(`RedisCacheMgr::deepDel: choose direction ${direction}`);
if (direction === CacheDelDirection.CHILD_TO_PARENT) {
const childKey = await this.get(key, CacheGetType.TYPE_OBJECT);
// given a child key, delete all keys in corresponding parent lists
const scopeList = this.getParents(childKey);
for (const listKey of scopeList) {
// get target list
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
if (!list.length) {
continue;
}
// remove target Key
list = list.filter((k) => k !== key);
// delete list
log(`RedisCacheMgr::deepDel: remove listKey ${listKey}`);
await this.del(listKey);
if (list.length) {
// set target list
log(`RedisCacheMgr::deepDel: set key ${listKey}`);
await this.set(listKey, list);
}
}
log(`RedisCacheMgr::deepDel: remove key ${key}`);
return await this.del(key);
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) {
key = /:list$/.test(key) ? key : `${key}:list`;
// given a list key, delete all the children
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY);
// delete each child key
await this.del(listOfChildren);
// delete list key
return await this.del(key);
} else {
log(`Invalid deepDel direction found : ${direction}`);
return Promise.resolve(false);
}
}
async appendToList(
scope: string,
subListKeys: string[],
key: string,
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
log(`RedisCacheMgr::appendToList: append key ${key} to ${listKey}`);
let list = await this.get(listKey, CacheGetType.TYPE_ARRAY);
if (!list || !list.length) {
return false;
}
if (list.includes('NONE')) {
list = [];
await this.del(listKey);
}
log(`RedisCacheMgr::appendToList: get key ${key}`);
// get Get Key
const value = await this.get(key, CacheGetType.TYPE_OBJECT);
log(`RedisCacheMgr::appendToList: preparing key ${key}`);
if (!value) {
// FALLBACK: this is to get rid of all keys that would be effected by this (should never happen)
console.error(`RedisCacheMgr::appendToList: value is empty for ${key}`);
const allParents = [];
// get all children
const listValues = await this.getList(scope, subListKeys);
// get all parents from children
listValues.list.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return false;
}
// prepare Get Key
const preparedValue = await this.prepareValue(
value,
this.getParents(value),
listKey,
);
// set Get Key
log(`RedisCacheMgr::appendToList: setting key ${key}`);
await this.set(
key,
JSON.stringify(preparedValue, this.getCircularReplacer()),
);
list.push(key);
return this.set(listKey, list);
}
prepareValue(value, listKeys = [], newParent?) {
if (newParent) {
listKeys.push(newParent);
}
if (value && typeof value === 'object') {
value[CacheListProp] = listKeys;
} else if (value && typeof value === 'string') {
const keyHelper = value.split(CacheListProp);
if (listKeys.length) {
value = `${keyHelper[0]}${CacheListProp}${listKeys.join(',')}`;
}
} else if (value) {
console.error(
`RedisCacheMgr::prepareListKey: keyValue is not object or string`,
value,
);
throw new Error(
`RedisCacheMgr::prepareListKey: keyValue is not object or string`,
);
}
return value;
}
getParents(value) {
if (value && typeof value === 'object') {
if (CacheListProp in value) {
const listsForKey = value[CacheListProp];
if (listsForKey && listsForKey.length) {
return listsForKey;
}
}
} else if (value && typeof value === 'string') {
if (value.includes(CacheListProp)) {
const keyHelper = value.split(CacheListProp);
const listsForKey = keyHelper[1].split(',');
if (listsForKey.length) {
return listsForKey;
}
}
}
return [];
}
async destroy(): Promise<boolean> {
log('RedisCacheMgr::destroy: destroy redis');
return this.client.flushdb().then((r) => r === 'OK');
}
async export(): Promise<any> {
log('RedisCacheMgr::export: export data');
const data = await this.client.keys('*');
const res = {};
return await Promise.all(
data.map(async (k) => {
res[k] = await this.get(
k,
k.slice(-4) === 'list'
? CacheGetType.TYPE_ARRAY
: CacheGetType.TYPE_OBJECT,
);
}),
).then(() => {
return res;
});
} }
} }

420
packages/nocodb/src/cache/RedisMockCacheMgr.ts vendored

@ -1,18 +1,10 @@
import debug from 'debug'; import debug from 'debug';
import Redis from 'ioredis-mock'; import Redis from 'ioredis-mock';
import CacheMgr from './CacheMgr'; import CacheMgr from './CacheMgr';
import type IORedis from 'ioredis';
import {
CacheDelDirection,
CacheGetType,
CacheListProp,
} from '~/utils/globals';
const log = debug('nc:cache');
export default class RedisMockCacheMgr extends CacheMgr { const _log = debug('nc:cache');
client: IORedis;
prefix: string;
export default class RedisMockCacheMgr extends CacheMgr {
constructor() { constructor() {
super(); super();
this.client = new Redis(); this.client = new Redis();
@ -22,412 +14,6 @@ export default class RedisMockCacheMgr extends CacheMgr {
// TODO(cache): fetch orgs once it's implemented // TODO(cache): fetch orgs once it's implemented
const orgs = 'noco'; const orgs = 'noco';
this.prefix = `nc:${orgs}`; this.prefix = `nc:${orgs}`;
} this.context = 'RedisMockCacheMgr';
// avoid circular structure to JSON
getCircularReplacer = () => {
const seen = new WeakSet();
return (_, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
// @ts-ignore
async del(key: string[] | string): Promise<any> {
log(`RedisMockCacheMgr::del: deleting key ${key}`);
if (Array.isArray(key)) {
if (key.length) {
return this.client.del(key);
}
} else if (key) {
return this.client.del(key);
}
}
// @ts-ignore
async get(key: string, type: string): Promise<any> {
log(`RedisMockCacheMgr::get: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key);
} else if (type === CacheGetType.TYPE_OBJECT) {
const res = await this.client.get(key);
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
if (
o &&
Object.keys(o).length === 0 &&
Object.getPrototypeOf(o) === Object.prototype
) {
log(`RedisMockCacheMgr::get: object is empty!`);
}
return Promise.resolve(o);
}
} catch (e) {
return Promise.resolve(res);
}
return Promise.resolve(res);
} else if (type === CacheGetType.TYPE_STRING) {
return await this.client.get(key);
}
log(`Invalid CacheGetType: ${type}`);
return Promise.resolve(false);
}
// @ts-ignore
async set(key: string, value: any): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(`RedisMockCacheMgr::set: setting key ${key} with value ${value}`);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
const keyValue = await this.get(key, CacheGetType.TYPE_OBJECT);
if (keyValue) {
value = await this.prepareValue(value, this.getParents(keyValue));
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
);
}
return this.client.set(key, value);
} else {
log(`RedisMockCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async setExpiring(key: string, value: any, seconds: number): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(
`RedisMockCacheMgr::setExpiring: setting key ${key} with value ${value} for ${seconds} seconds`,
);
// TODO: better way to handle expiration in mock redis
setTimeout(() => {
this.del(key);
}, seconds * 1000);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
);
}
return this.client.set(key, value);
} else {
log(`RedisMockCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async incrby(key: string, value = 1): Promise<any> {
return this.client.incrby(key, value);
}
async getList(
scope: string,
subKeys: string[],
): Promise<{
list: any[];
isNoneList: boolean;
}> {
// remove null from arrays
subKeys = subKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const key =
subKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`;
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || [];
log(`RedisMockCacheMgr::getList: getting list with key ${key}`);
const isNoneList = arr.length && arr[0] === 'NONE';
if (isNoneList || !arr.length) {
return Promise.resolve({
list: [],
isNoneList,
});
}
log(`RedisMockCacheMgr::getList: getting list with keys ${arr}`);
const values = await this.client.mget(arr);
if (values.some((v) => v === null)) {
// FALLBACK: a key is missing from list, this should never happen
console.error(`RedisMockCacheMgr::getList: missing value for ${key}`);
const allParents = [];
// get all parents from children
values.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return Promise.resolve({
list: [],
isNoneList,
});
}
return {
list: values.map((res) => {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
return o;
}
} catch (e) {
return res;
}
return res;
}),
isNoneList,
};
}
async setList(
scope: string,
subListKeys: string[],
list: any[],
props: string[] = [],
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// construct key for List
// e.g. nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
if (!list.length) {
// Set NONE here so that it won't hit the DB on each page load
return this.set(listKey, ['NONE']);
}
// fetch existing list
const listOfGetKeys =
(await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
for (const o of list) {
// construct key for Get
let getKey = `${this.prefix}:${scope}:${o.id}`;
if (props.length) {
const propValues = props.map((p) => o[p]);
// e.g. nc:<orgs>:<scope>:<prop_value_1>:<prop_value_2>
getKey = `${this.prefix}:${scope}:${propValues.join(':')}`;
}
log(`RedisMockCacheMgr::setList: get key ${getKey}`);
// get Get Key
let value = await this.get(getKey, CacheGetType.TYPE_OBJECT);
if (value) {
log(`RedisMockCacheMgr::setList: preparing key ${getKey}`);
// prepare Get Key
value = await this.prepareValue(o, this.getParents(value), listKey);
} else {
value = await this.prepareValue(o, [], listKey);
}
// set Get Key
log(`RedisMockCacheMgr::setList: setting key ${getKey}`);
await this.set(getKey, JSON.stringify(value, this.getCircularReplacer()));
// push Get Key to List
listOfGetKeys.push(getKey);
}
// set List Key
log(`RedisMockCacheMgr::setList: setting list with key ${listKey}`);
return this.set(listKey, listOfGetKeys);
}
async deepDel(
scope: string,
key: string,
direction: string,
): Promise<boolean> {
log(`RedisMockCacheMgr::deepDel: choose direction ${direction}`);
if (direction === CacheDelDirection.CHILD_TO_PARENT) {
const childKey = await this.get(key, CacheGetType.TYPE_OBJECT);
// given a child key, delete all keys in corresponding parent lists
const scopeList = this.getParents(childKey);
for (const listKey of scopeList) {
// get target list
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
if (!list.length) {
continue;
}
// remove target Key
list = list.filter((k) => k !== key);
// delete list
log(`RedisMockCacheMgr::deepDel: remove listKey ${listKey}`);
await this.del(listKey);
if (list.length) {
// set target list
log(`RedisMockCacheMgr::deepDel: set key ${listKey}`);
await this.set(listKey, list);
}
}
log(`RedisMockCacheMgr::deepDel: remove key ${key}`);
return await this.del(key);
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) {
key = /:list$/.test(key) ? key : `${key}:list`;
// given a list key, delete all the children
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY);
// delete each child key
await this.del(listOfChildren);
// delete list key
return await this.del(key);
} else {
log(`Invalid deepDel direction found : ${direction}`);
return Promise.resolve(false);
}
}
async appendToList(
scope: string,
subListKeys: string[],
key: string,
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
log(`RedisMockCacheMgr::appendToList: append key ${key} to ${listKey}`);
let list = await this.get(listKey, CacheGetType.TYPE_ARRAY);
if (!list || !list.length) {
return false;
}
if (list[0] === 'NONE') {
list = [];
await this.del(listKey);
}
log(`RedisMockCacheMgr::appendToList: get key ${key}`);
// get Get Key
const value = await this.get(key, CacheGetType.TYPE_OBJECT);
log(`RedisMockCacheMgr::appendToList: preparing key ${key}`);
if (!value) {
// FALLBACK: this is to get rid of all keys that would be effected by this (should never happen)
console.error(
`RedisMockCacheMgr::appendToList: value is empty for ${key}`,
);
const allParents = [];
// get all children
const listValues = await this.getList(scope, subListKeys);
// get all parents from children
listValues.list.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return false;
}
// prepare Get Key
const preparedValue = await this.prepareValue(
value,
this.getParents(value),
listKey,
);
// set Get Key
log(`RedisMockCacheMgr::appendToList: setting key ${key}`);
await this.set(
key,
JSON.stringify(preparedValue, this.getCircularReplacer()),
);
list.push(key);
return this.set(listKey, list);
}
prepareValue(value, listKeys = [], newParent?) {
if (newParent) {
listKeys.push(newParent);
}
if (value && typeof value === 'object') {
value[CacheListProp] = listKeys;
} else if (value && typeof value === 'string') {
const keyHelper = value.split(CacheListProp);
if (listKeys.length) {
value = `${keyHelper[0]}${CacheListProp}${listKeys.join(',')}`;
}
} else if (value) {
console.error(
`RedisMockCacheMgr::prepareListKey: keyValue is not object or string`,
value,
);
throw new Error(
`RedisMockCacheMgr::prepareListKey: keyValue is not object or string`,
);
}
return value;
}
getParents(value) {
if (value && typeof value === 'object') {
if (CacheListProp in value) {
const listsForKey = value[CacheListProp];
if (listsForKey && listsForKey.length) {
return listsForKey;
}
}
} else if (value && typeof value === 'string') {
if (value.includes(CacheListProp)) {
const keyHelper = value.split(CacheListProp);
const listsForKey = keyHelper[1].split(',');
if (listsForKey.length) {
return listsForKey;
}
}
}
return [];
}
async destroy(): Promise<boolean> {
log('RedisMockCacheMgr::destroy: destroy redis');
return this.client.flushdb().then((r) => r === 'OK');
}
async export(): Promise<any> {
log('RedisMockCacheMgr::export: export data');
const data = await this.client.keys('*');
const res = {};
return await Promise.all(
data.map(async (k) => {
res[k] = await this.get(
k,
k.slice(-4) === 'list'
? CacheGetType.TYPE_ARRAY
: CacheGetType.TYPE_OBJECT,
);
}),
).then(() => {
return res;
});
} }
} }

1
packages/nocodb/src/models/ApiToken.ts

@ -56,7 +56,6 @@ export default class ApiToken implements ApiTokenType {
static async delete(token, ncMeta = Noco.ncMeta) { static async delete(token, ncMeta = Noco.ncMeta) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.API_TOKEN,
`${CacheScope.API_TOKEN}:${token}`, `${CacheScope.API_TOKEN}:${token}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/Base.ts

@ -240,7 +240,6 @@ export default class Base implements BaseType {
// remove item in cache list // remove item in cache list
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.PROJECT,
`${CacheScope.PROJECT}:${baseId}`, `${CacheScope.PROJECT}:${baseId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -353,7 +352,6 @@ export default class Base implements BaseType {
} }
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.PROJECT,
`${CacheScope.PROJECT}:${baseId}`, `${CacheScope.PROJECT}:${baseId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

14
packages/nocodb/src/models/Column.ts

@ -481,7 +481,6 @@ export default class Column<T = any> implements ColumnType {
public static async clearList({ fk_model_id }) { public static async clearList({ fk_model_id }) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COLUMN,
`${CacheScope.COLUMN}:${fk_model_id}:list`, `${CacheScope.COLUMN}:${fk_model_id}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );
@ -822,7 +821,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: col.id, fk_column_id: col.id,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
cacheScopeName,
`${cacheScopeName}:${col.id}`, `${cacheScopeName}:${col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -838,7 +836,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (gridViewColumnId) { if (gridViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.GRID_VIEW_COLUMN,
`${CacheScope.GRID_VIEW_COLUMN}:${gridViewColumnId}`, `${CacheScope.GRID_VIEW_COLUMN}:${gridViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -854,7 +851,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (formViewColumnId) { if (formViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FORM_VIEW_COLUMN,
`${CacheScope.FORM_VIEW_COLUMN}:${formViewColumnId}`, `${CacheScope.FORM_VIEW_COLUMN}:${formViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -870,7 +866,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (kanbanViewColumnId) { if (kanbanViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.KANBAN_VIEW_COLUMN,
`${CacheScope.KANBAN_VIEW_COLUMN}:${kanbanViewColumnId}`, `${CacheScope.KANBAN_VIEW_COLUMN}:${kanbanViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -886,7 +881,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (galleryViewColumnId) { if (galleryViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.GALLERY_VIEW_COLUMN,
`${CacheScope.GALLERY_VIEW_COLUMN}:${galleryViewColumnId}`, `${CacheScope.GALLERY_VIEW_COLUMN}:${galleryViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -917,7 +911,6 @@ export default class Column<T = any> implements ColumnType {
// Columns // Columns
await ncMeta.metaDelete(null, null, MetaTable.COLUMNS, col.id); await ncMeta.metaDelete(null, null, MetaTable.COLUMNS, col.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COLUMN,
`${CacheScope.COLUMN}:${col.id}`, `${CacheScope.COLUMN}:${col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -944,7 +937,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: colId, fk_column_id: colId,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_LOOKUP,
`${CacheScope.COL_LOOKUP}:${colId}`, `${CacheScope.COL_LOOKUP}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -955,7 +947,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: colId, fk_column_id: colId,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_ROLLUP,
`${CacheScope.COL_ROLLUP}:${colId}`, `${CacheScope.COL_ROLLUP}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -967,7 +958,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: colId, fk_column_id: colId,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_RELATION,
`${CacheScope.COL_RELATION}:${colId}`, `${CacheScope.COL_RELATION}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -979,7 +969,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_FORMULA,
`${CacheScope.COL_FORMULA}:${colId}`, `${CacheScope.COL_FORMULA}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -991,7 +980,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_QRCODE,
`${CacheScope.COL_QRCODE}:${colId}`, `${CacheScope.COL_QRCODE}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -1004,7 +992,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_BARCODE,
`${CacheScope.COL_BARCODE}:${colId}`, `${CacheScope.COL_BARCODE}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -1018,7 +1005,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_SELECT_OPTION,
`${CacheScope.COL_SELECT_OPTION}:${colId}:list`, `${CacheScope.COL_SELECT_OPTION}:${colId}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );

3
packages/nocodb/src/models/Filter.ts

@ -276,7 +276,6 @@ export default class Filter implements FilterType {
await deleteRecursively(f); await deleteRecursively(f);
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -435,7 +434,6 @@ export default class Filter implements FilterType {
if (filter.id) { if (filter.id) {
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -459,7 +457,6 @@ export default class Filter implements FilterType {
if (filter.id) { if (filter.id) {
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/Hook.ts

@ -238,7 +238,6 @@ export default class Hook implements HookType {
); );
for (const filter of filterList) { for (const filter of filterList) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -246,7 +245,6 @@ export default class Hook implements HookType {
} }
// Delete Hook // Delete Hook
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.HOOK,
`${CacheScope.HOOK}:${hookId}`, `${CacheScope.HOOK}:${hookId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/HookFilter.ts

@ -174,7 +174,6 @@ export default class Filter {
await deleteRecursively(f); await deleteRecursively(f);
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -307,7 +306,6 @@ export default class Filter {
// if (filter.id) { // if (filter.id) {
// await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); // await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
// await NocoCache.deepDel( // await NocoCache.deepDel(
// CacheScope.FILTER_EXP,
// `${CacheScope.FILTER_EXP}:${filter.id}`, // `${CacheScope.FILTER_EXP}:${filter.id}`,
// CacheDelDirection.CHILD_TO_PARENT // CacheDelDirection.CHILD_TO_PARENT
// ); // );

4
packages/nocodb/src/models/Model.ts

@ -463,7 +463,6 @@ export default class Model implements TableType {
fk_column_id: col.id, fk_column_id: col.id,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
cacheScopeName,
`${cacheScopeName}:${col.id}`, `${cacheScopeName}:${col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -484,7 +483,6 @@ export default class Model implements TableType {
for (const col of leftOverColumns) { for (const col of leftOverColumns) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_RELATION,
`${CacheScope.COL_RELATION}:${col.fk_column_id}`, `${CacheScope.COL_RELATION}:${col.fk_column_id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -496,7 +494,6 @@ export default class Model implements TableType {
} }
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COLUMN,
`${CacheScope.COLUMN}:${this.id}`, `${CacheScope.COLUMN}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -505,7 +502,6 @@ export default class Model implements TableType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.MODEL,
`${CacheScope.MODEL}:${this.id}`, `${CacheScope.MODEL}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

1
packages/nocodb/src/models/ModelRoleVisibility.ts

@ -124,7 +124,6 @@ export default class ModelRoleVisibility implements ModelRoleVisibilityType {
}, },
); );
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.MODEL_ROLE_VISIBILITY,
`${CacheScope.MODEL_ROLE_VISIBILITY}:${fk_view_id}:${role}`, `${CacheScope.MODEL_ROLE_VISIBILITY}:${fk_view_id}:${role}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/Sort.ts

@ -27,7 +27,6 @@ export default class Sort {
public static async deleteAll(viewId: string, ncMeta = Noco.ncMeta) { public static async deleteAll(viewId: string, ncMeta = Noco.ncMeta) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.SORT,
`${CacheScope.SORT}:${viewId}`, `${CacheScope.SORT}:${viewId}`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );
@ -187,7 +186,6 @@ export default class Sort {
await ncMeta.metaDelete(null, null, MetaTable.SORT, sortId); await ncMeta.metaDelete(null, null, MetaTable.SORT, sortId);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.SORT,
`${CacheScope.SORT}:${sortId}`, `${CacheScope.SORT}:${sortId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

3
packages/nocodb/src/models/Source.ts

@ -414,7 +414,6 @@ export default class Source implements SourceType {
fk_column_id: relCol.col.id, fk_column_id: relCol.col.id,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
relCol.cacheScopeName,
`${relCol.cacheScopeName}:${relCol.col.id}`, `${relCol.cacheScopeName}:${relCol.col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -434,7 +433,6 @@ export default class Source implements SourceType {
const res = await ncMeta.metaDelete(null, null, MetaTable.BASES, this.id); const res = await ncMeta.metaDelete(null, null, MetaTable.BASES, this.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE,
`${CacheScope.BASE}:${this.id}`, `${CacheScope.BASE}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -460,7 +458,6 @@ export default class Source implements SourceType {
); );
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE,
`${CacheScope.BASE}:${this.id}`, `${CacheScope.BASE}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/User.ts

@ -75,7 +75,6 @@ export default class User implements UserType {
const bases = await Base.list({}, ncMeta); const bases = await Base.list({}, ncMeta);
for (const base of bases) { for (const base of bases) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE_USER,
`${CacheScope.BASE_USER}:${base.id}:list`, `${CacheScope.BASE_USER}:${base.id}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );
@ -299,7 +298,6 @@ export default class User implements UserType {
for (const base of bases) { for (const base of bases) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE_USER,
`${CacheScope.BASE_USER}:${base.id}:list`, `${CacheScope.BASE_USER}:${base.id}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );

3
packages/nocodb/src/models/View.ts

@ -1139,17 +1139,14 @@ export default class View implements ViewType {
}); });
await ncMeta.metaDelete(null, null, MetaTable.VIEWS, viewId); await ncMeta.metaDelete(null, null, MetaTable.VIEWS, viewId);
await NocoCache.deepDel( await NocoCache.deepDel(
tableScope,
`${tableScope}:${viewId}`, `${tableScope}:${viewId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
await NocoCache.deepDel( await NocoCache.deepDel(
columnTableScope,
`${columnTableScope}:${viewId}`, `${columnTableScope}:${viewId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.VIEW,
`${CacheScope.VIEW}:${viewId}`, `${CacheScope.VIEW}:${viewId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/utils/globals.ts

@ -176,8 +176,6 @@ export enum CacheDelDirection {
CHILD_TO_PARENT = 'CHILD_TO_PARENT', CHILD_TO_PARENT = 'CHILD_TO_PARENT',
} }
export const CacheListProp = '__nc_list__';
export const GROUPBY_COMPARISON_OPS = <const>[ export const GROUPBY_COMPARISON_OPS = <const>[
// these are used for groupby // these are used for groupby
'gb_eq', 'gb_eq',

Loading…
Cancel
Save