mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
556 lines
13 KiB
556 lines
13 KiB
const delay = t => new Promise(res => setTimeout(() => res(), t)) |
|
const Dataloader = require('dataloader') |
|
const time = 1000; |
|
const knex = require('knex')({ |
|
client: 'mysql2', |
|
connection: { |
|
user: 'root', |
|
password: 'password', |
|
database: 'sakila' |
|
} |
|
}); |
|
|
|
|
|
|
|
|
|
class Country { |
|
constructor(data) { |
|
Object.assign(this, data) |
|
} |
|
|
|
async cityList() { |
|
return (await knex('city').select('*').where({ |
|
country_id: this.country_id |
|
}).limit(1)).map(c => new City(c)) |
|
} |
|
|
|
async cityCount() { |
|
return (await knex('city').count('city_id as count').where({ |
|
country_id: this.country_id |
|
}).first()).count |
|
} |
|
|
|
} |
|
|
|
|
|
class Address { |
|
constructor(data) { |
|
Object.assign(this, data) |
|
} |
|
|
|
async City() { |
|
return new City(await knex('city').select('*').where({ |
|
city_id: this.city_id |
|
}).first()) |
|
} |
|
|
|
|
|
} |
|
|
|
class City { |
|
constructor(data) { |
|
Object.assign(this, data) |
|
} |
|
|
|
async Country() { |
|
return new Country((await knex('country').select('*').where({ |
|
country_id: this.country_id |
|
}).first())) |
|
} |
|
|
|
async addressList() { |
|
return (await knex('address').select('*').where({ |
|
city_id: this.city_id |
|
})).map(c => new Address(c)) |
|
} |
|
|
|
async addressCount() { |
|
return (await knex('address').count('city_id as count').where({ |
|
city_id: this.city_id |
|
}).first()).count |
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// const nestResolver = { |
|
// country: `INDIA`, |
|
// async cityCount() { |
|
// await delay(time) |
|
// return `12-${time}` |
|
// }, |
|
// async cityList() { |
|
// await delay(time) |
|
// return { |
|
// city: `city 1-${time}`, |
|
// async addressCount() { |
|
// await delay(time) |
|
// return `2-${time}` |
|
// }, |
|
// } |
|
// } |
|
// }; |
|
|
|
|
|
const rootAst = { |
|
Country: { |
|
name: 'Country', |
|
type: 'Object', |
|
fields: { |
|
country_id: { |
|
name: 'country_id', |
|
type: 'number' |
|
}, country: { |
|
name: 'country', |
|
type: 'string' |
|
}, |
|
|
|
cityList: { |
|
name: 'cityList', |
|
type: 'array', |
|
elementType: 'City', |
|
// nested: { |
|
// level: 1, path: ['country_id'] |
|
// } |
|
}, |
|
cityCount: { |
|
name: 'cityCount', |
|
type: 'number', |
|
// nested: { |
|
// level: 1, path: ['country_id'] |
|
// } |
|
}, |
|
|
|
|
|
addressCount: { |
|
name: 'addressCount', |
|
type: 'number', |
|
nested: {level: 2, path: ['cityList', 'addressCount']} |
|
}, |
|
c: { |
|
name: 'addressCount', |
|
type: 'number', |
|
nested: {level: 2, path: ['cityList', 'addressList','address']} |
|
}, |
|
d: { |
|
name: 'addressCount', |
|
type: 'number', |
|
nested: {level: 2, path: ['cityList', 'addressList','City', 'Country']} |
|
} |
|
} |
|
}, City: { |
|
name: 'City', |
|
type: 'Object', |
|
fields: { |
|
city_id: { |
|
name: 'city_id', |
|
type: 'number' |
|
}, country_id: { |
|
name: 'country_id', |
|
type: 'number' |
|
}, city: { |
|
name: 'city', |
|
type: 'string' |
|
}, addressList: { |
|
name: 'addressList', |
|
type: 'array', |
|
elementType: 'Address' |
|
}, countryRead: { |
|
name: 'countryRead', |
|
type: 'Country' |
|
}, addressCount: { |
|
name: 'addressCount', |
|
type: 'number' |
|
}, |
|
Country: { |
|
name: 'Country', |
|
type: 'Country', |
|
// nested: { |
|
// level: 1, path: ['country_id'] |
|
// } |
|
}, |
|
} |
|
}, Address: { |
|
name: 'Address', |
|
type: 'Object', |
|
fields: { |
|
address: { |
|
name: 'address', |
|
type: 'string' |
|
}, |
|
City: { |
|
name: 'City', |
|
type: 'City', |
|
// nested: { |
|
// level: 1, |
|
// path: ['city_id'] |
|
// } |
|
}, |
|
Country: { |
|
name: 'Country', |
|
type: 'Country', |
|
nested: { |
|
level: 1, |
|
path: ['City', 'Country','country'] |
|
} |
|
} |
|
} |
|
}, |
|
CountryList: { |
|
type: 'array', |
|
elementType: 'Country' |
|
}, |
|
AddressList: { |
|
type: 'array', |
|
elementType: 'Address' |
|
}, |
|
} |
|
|
|
|
|
const nestResolver = { |
|
async Country() { |
|
return new Country(await knex('country').first()) |
|
}, |
|
|
|
async CountryList() { |
|
return (await knex('country').limit(2)).map(c => new Country(c)) |
|
}, |
|
|
|
async AddressList() { |
|
return (await knex('address').limit(10)).map(c => new Address(c)) |
|
} |
|
} |
|
|
|
|
|
/* |
|
|
|
const reqExecutor = async (reqObj, resObj, _ast) => { |
|
|
|
const res = {} |
|
// const dependedFields = Object.keys(reqObj).map(k => (ast.fields && ast.fields[k] && ast.fields[k].nested && ast.fields[k].nested.path)) |
|
/!* const dependFields = new Set(); |
|
for(const k of Object.keys(reqObj)){ |
|
if(ast.fields && ast.fields[k] && ast.fields[k].nested && ast.fields[k].nested.path){ |
|
dependFields.add(ast.fields[k].nested.path) |
|
|
|
} |
|
}*!/ |
|
|
|
function extractDependsOn(key, i = 0, prev, __ast) { |
|
const ast = __ast || _ast |
|
if (!prev || typeof prev !== 'object' || i >= _ast.fields[key].nested.path.length) { |
|
if (typeof resObj[key] === 'function') { |
|
return resObj[key](prev)//.call(res); |
|
// console.log(prefix + res[key]) |
|
} else if (typeof resObj[key] === 'object') { |
|
return Promise.resolve(resObj[key]) |
|
// console.log(prefix + res[key]) |
|
} else { |
|
return Promise.resolve(resObj[key]) |
|
// console.log(prefix + res[key]) |
|
} |
|
} |
|
if (typeof prev[key] === 'function') { |
|
return resObj[key](res)//.call(res); |
|
// console.log(prefix + res[key]) |
|
} |
|
|
|
|
|
if (!prev[_ast.fields[key].nested.path[i]]) |
|
extractField(_ast.fields[key].nested.path[i], _ast) |
|
prev[key] = prev[_ast.fields[key].nested.path[i]].then(next => { |
|
// return extractDependsOn(key, ++i, next) |
|
|
|
|
|
if (Array.isArray(next)) { |
|
return Promise.all(next.map(r => extractDependsOn(key, ++i, r, rootAst[_ast[ast.fields[key].nested.path[i]].elementType]))) |
|
} else { |
|
return extractDependsOn(reqObj[key], ++i, next, _ast[ast.fields[key].nested.path[i]]) |
|
} |
|
|
|
}) |
|
return prev[key] |
|
} |
|
|
|
|
|
function extractField(key, __ast) { |
|
const ast = __ast || _ast |
|
|
|
if (!(ast.fields && ast.fields[key] && ast.fields[key].nested)) { |
|
if (typeof resObj[key] === 'function') { |
|
res[key] = resObj[key]()//.call(res); |
|
// console.log(prefix + res[key]) |
|
} else if (typeof resObj[key] === 'object') { |
|
res[key] = Promise.resolve(resObj[key]) |
|
// console.log(prefix + res[key]) |
|
} else { |
|
res[key] = Promise.resolve(resObj[key]) |
|
// console.log(prefix + res[key]) |
|
} |
|
} else { |
|
extractDependsOn(key, 0, res) |
|
} |
|
|
|
|
|
} |
|
|
|
|
|
for (const key of Object.keys(reqObj)) { |
|
if (key in resObj) { |
|
extractField(key); |
|
} |
|
|
|
|
|
if (reqObj[key] && typeof reqObj[key] === 'object') { |
|
res[key] = res[key].then(res1 => { |
|
if (Array.isArray(res1)) { |
|
return Promise.all(res1.map(r => reqExecutor(reqObj[key], r, rootAst[_ast[key].elementType]))) |
|
} else { |
|
return reqExecutor(reqObj[key], res1, _ast[key]) |
|
} |
|
}) |
|
} |
|
|
|
|
|
} |
|
|
|
await Promise.all(Object.values(res)) |
|
|
|
const out = {}; |
|
for (const [k, v] of Object.entries(res)) { |
|
out[k] = await v |
|
} |
|
|
|
|
|
return out |
|
} |
|
*/ |
|
|
|
/* |
|
const reqExecutor = async (reqObj, resObj, ast) => { |
|
|
|
const res = {} |
|
|
|
|
|
await Promise.all(Object.keys(reqObj).map(async (key) => { |
|
if (key in resObj && !(ast.fields && ast.fields[key] && ast.fields[key].path)) { |
|
|
|
if (typeof resObj[key] === 'function') { |
|
res[key] = await resObj[key]()//.call(res); |
|
// console.log(prefix + res[key]) |
|
} else if (typeof resObj[key] === 'object') { |
|
res[key] = resObj[key] |
|
// console.log(prefix + res[key]) |
|
} else { |
|
res[key] = resObj[key] |
|
// console.log(prefix + res[key]) |
|
} |
|
await delay(100) |
|
} |
|
if (reqObj[key] && typeof reqObj[key] === 'object') { |
|
if (Array.isArray(res[key])) { |
|
res[key] = await Promise.all(res[key].map(r => reqExecutor(reqObj[key], r, rootAst[ast[key].elementType]))) |
|
} else { |
|
res[key] = await reqExecutor(reqObj[key], res[key], ast[key]) |
|
} |
|
} |
|
|
|
})) |
|
|
|
return res |
|
} |
|
|
|
*/ |
|
|
|
|
|
const execute = async (requestObj, resolverObj, ast) => { |
|
|
|
const res = {} |
|
// const dependedFields = Object.keys(reqObj).map(k => (ast.fields && ast.fields[k] && ast.fields[k].nested && ast.fields[k].nested.path)) |
|
/* const dependFields = new Set(); |
|
for(const k of Object.keys(reqObj)){ |
|
if(ast.fields && ast.fields[k] && ast.fields[k].nested && ast.fields[k].nested.path){ |
|
dependFields.add(ast.fields[k].nested.path) |
|
|
|
} |
|
}*/ |
|
|
|
|
|
// extract nested(lookup) recursively |
|
const extractNested = (path, o = {}, resolver) => { |
|
if (path.length) { |
|
const key = path[0] |
|
if (!o[key]) { |
|
|
|
|
|
if (typeof resolver[key] === 'function') { |
|
o[path[0]] = resolver[key]()//.call(res); |
|
// console.log(prefix + o[path[0]]) |
|
} else if (typeof resolver[key] === 'object') { |
|
o[path[0]] = Promise.resolve(resolver[key]) |
|
// console.log(prefix + o[path[0]]) |
|
} else { |
|
o[path[0]] = Promise.resolve(resolver[key]) |
|
// console.log(prefix + o[path[0]]) |
|
} |
|
|
|
|
|
} else if (typeof o[key] === 'function') { |
|
o[key] = o[key]() |
|
} |
|
|
|
|
|
return (o[path[0]] instanceof Promise ? o[path[0]] : Promise.resolve(o[path[0]])).then(res1 => { |
|
|
|
if (Array.isArray(res1)) { |
|
return Promise.all(res1.map(r => extractNested(path.slice(1), r))) |
|
} else { |
|
return extractNested(path.slice(1), res1) |
|
} |
|
|
|
}) |
|
} else { |
|
return Promise.resolve(o) |
|
} |
|
}; |
|
|
|
|
|
// function for extracting field |
|
function extractField(key) { |
|
if (!(ast && ast && ast[key] && ast[key].nested)) { |
|
if (resolverObj) { |
|
// resolve if it's resolver function |
|
if (typeof resolverObj[key] === 'function') { |
|
res[key] = resolverObj[key]()//.call(res); |
|
// console.log(prefix + res[key]) |
|
} else if (typeof resolverObj[key] === 'object') { |
|
res[key] = Promise.resolve(resolverObj[key]) |
|
// console.log(prefix + res[key]) |
|
} else { |
|
res[key] = Promise.resolve(resolverObj[key]) |
|
// console.log(prefix + res[key]) |
|
} |
|
} |
|
} else { |
|
/* if (!res[ast[key].nested]) |
|
extractField(ast[key].nested.path) |
|
|
|
|
|
res[key] = res[ast.fields[key].nested.path].then(res => { |
|
if (typeof resolverObj[key] === 'function') { |
|
return resolverObj[key](res)//.call(res); |
|
// console.log(prefix + res[key]) |
|
} else if (typeof resolverObj[key] === 'object') { |
|
return Promise.resolve(resolverObj[key]) |
|
// console.log(prefix + res[key]) |
|
} else { |
|
return Promise.resolve(resolverObj[key]) |
|
// console.log(prefix + res[key]) |
|
} |
|
})*/ |
|
// if nested extract the nested value |
|
res[key] = extractNested(ast[key].nested.path, res, resolverObj) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
for (const key of Object.keys(requestObj)) { |
|
// if (key in resolverObj) { |
|
extractField(key); |
|
// } |
|
|
|
|
|
if (requestObj[key] && typeof requestObj[key] === 'object') { |
|
res[key] = res[key].then(res1 => { |
|
if (Array.isArray(res1)) { |
|
return Promise.all(res1.map(r => execute(requestObj[key], r, rootAst[ast[key].elementType] && rootAst[ast[key].elementType].fields))) |
|
} else { |
|
return execute(requestObj[key], res1, ast[key].fields) |
|
} |
|
}) |
|
} |
|
|
|
|
|
} |
|
|
|
await Promise.all(Object.values(res)) |
|
|
|
const out = {}; |
|
for (const [k, v] of Object.entries(res)) { |
|
if(k in requestObj) |
|
out[k] = await v |
|
} |
|
|
|
|
|
return out |
|
} |
|
|
|
|
|
const req = { |
|
AddressList: { |
|
// City: { |
|
// Country: 1 |
|
// }, |
|
Country: 1 |
|
}, |
|
CountryList: { |
|
// cityList: { |
|
// addressCount: 1, |
|
// addressList: { |
|
// address: 1 |
|
// } |
|
// }, |
|
// c:1, |
|
// d:1, |
|
addressCount:1 |
|
} |
|
}; |
|
|
|
|
|
const extractNested = (path, o = {}) => { |
|
if (path.length) { |
|
if (!o[path[0]]) { |
|
o[path[0]] = Promise.resolve({}) |
|
} |
|
return o[path[0]].then(r => extractNested(path.slice(1), r)) |
|
} else { |
|
return Promise.resolve(o) |
|
} |
|
}; |
|
|
|
(async () => { |
|
|
|
console.time('start') |
|
console.log(JSON.stringify(await execute(req, nestResolver, rootAst), 0, 2)); |
|
console.timeEnd('start') |
|
|
|
|
|
/* console.log(JSON.stringify(await extractNested(['a', 'b'], { |
|
a: Promise.resolve({ |
|
b: Promise.resolve({ |
|
t: 1 |
|
}) |
|
}) |
|
}), 0, 2))*/ |
|
|
|
|
|
/* const o = {};*/ |
|
|
|
/* console.log(JSON.stringify(await extractNested(['a', 'b'], o), 0, 2)) |
|
console.log(o) |
|
console.log(JSON.stringify(await extractNested(['a', 'c'], o), 0, 2)) |
|
console.log(JSON.stringify(await extractNested(['a', 'b', 'c'], o), 0, 2)) |
|
console.log(JSON.stringify(await extractNested(['a', 'b'], o), 0, 2))*/ |
|
|
|
})().catch(e => console.log(e)).finally(() => process.exit(0)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|