123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- const { asArray, isObject, uniqBy, get, set } = require('../utils/objectUtils');
- const { ref: createRef } = require('../queryBuilder/ReferenceBuilder');
- const { raw: createRaw } = require('../queryBuilder/RawBuilder');
- const { propToStr, PROP_KEY_PREFIX } = require('../model/modelValues');
- class ModelNotFoundError extends Error {
- constructor(tableName) {
- super();
- this.name = this.constructor.name;
- this.tableName = tableName;
- }
- }
- class InvalidReferenceError extends Error {
- constructor() {
- super();
- this.name = this.constructor.name;
- }
- }
- // A pair of these define how two tables are related to each other.
- // Both the owner and the related table have one of these.
- //
- // A relation property can be a single column, an array of columns
- // (composite key) a json column reference, an array of json column
- // references or any combination of the above.
- class RelationProperty {
- // references must be a reference string like `Table.column:maybe.some.json[1].path`.
- // or an array of such references (composite key).
- //
- // modelClassResolver must be a function that takes a table name
- // and returns a model class.
- constructor(references, modelClassResolver) {
- const refs = createRefs(asArray(references));
- const paths = createPaths(refs, modelClassResolver);
- const modelClass = resolveModelClass(paths);
- this._refs = refs;
- this._modelClass = modelClass;
- this._props = paths.map(it => it.path[0]);
- this._cols = refs.map(it => it.column);
- this._propGetters = paths.map(it => createGetter(it.path));
- this._propSetters = paths.map(it => createSetter(it.path));
- }
- static get ModelNotFoundError() {
- return ModelNotFoundError;
- }
- static get InvalidReferenceError() {
- return InvalidReferenceError;
- }
- // The number of columns.
- get size() {
- return this._refs.length;
- }
- // The model class that owns the property.
- get modelClass() {
- return this._modelClass;
- }
- // An array of property names. Contains multiple values in case of composite key.
- // This may be different from `cols` if the model class has some kind of conversion
- // between database and "external" formats, for example a snake_case to camelCase
- // conversion.
- get props() {
- return this._props;
- }
- // An array of column names. Contains multiple values in case of composite key.
- // This may be different from `props` if the model class has some kind of conversion
- // between database and "external" formats, for example a snake_case to camelCase
- // conversion.
- get cols() {
- return this._cols;
- }
- // Creates a concatenated string from the property values of the given object.
- propKey(obj) {
- const size = this.size;
- var key = PROP_KEY_PREFIX;
- for (let i = 0; i < size; ++i) {
- key += propToStr(this.getProp(obj, i));
- if (i !== size - 1) {
- key += ',';
- }
- }
- return key;
- }
- // Returns the property values of the given object as an array.
- getProps(obj) {
- const size = this.size;
- const props = new Array(size);
- for (let i = 0; i < size; ++i) {
- props[i] = this.getProp(obj, i);
- }
- return props;
- }
- // Returns the index:th property value of the given object.
- getProp(obj, index) {
- return this._propGetters[index](obj);
- }
- // Sets the index:th property value of the given object.
- setProp(obj, index, value) {
- return this._propSetters[index](obj, value);
- }
- // Returns the index:th column name with table reference. Something like
- // 'Table.someColumn'.
- fullCol(builder, index) {
- const table = builder.tableRefFor(this.modelClass);
- return `${table}.${this.cols[index]}`;
- }
- // Returns an instance of ReferenceBuilder that points to the index:th
- // value of a row.
- ref(builder, index) {
- const table = builder.tableRefFor(this.modelClass);
- return this._refs[index].clone().table(table);
- }
- // Returns an array of reference builder. `ref(builder, i)` for each i.
- refs(builder) {
- const refs = new Array(this.size);
- for (let i = 0, l = refs.length; i < l; ++i) {
- refs[i] = this.ref(builder, i);
- }
- return refs;
- }
- // Appends an update operation for the index:th column into `patch` object.
- patch(patch, index, value) {
- const ref = this._refs[index];
- if (ref.isPlainColumnRef) {
- patch[this._cols[index]] = value;
- } else {
- // Objection `patch`, `update` etc. methods understand field expressions.
- patch[ref.expression] = value;
- }
- }
- // String representation of this property's index:th column for logging.
- propDescription(index) {
- return this._refs[index].expression;
- }
- }
- function createRefs(refs) {
- try {
- return refs.map(it => {
- if (!isObject(it) || !it.isObjectionReferenceBuilder) {
- return createRef(it);
- } else {
- return it;
- }
- });
- } catch (err) {
- throw new InvalidReferenceError();
- }
- }
- function createPaths(refs, modelClassResolver) {
- return refs.map(ref => {
- if (!ref.tableName) {
- throw new InvalidReferenceError();
- }
- const modelClass = modelClassResolver(ref.tableName);
- if (!modelClass) {
- throw new ModelNotFoundError(ref.tableName);
- }
- const prop = modelClass.columnNameToPropertyName(ref.column);
- const jsonPath = ref.reference.access.map(it => it.ref);
- return {
- path: [prop].concat(jsonPath),
- modelClass
- };
- });
- }
- function resolveModelClass(paths) {
- const modelClasses = paths.map(it => it.modelClass);
- const uniqueModelClasses = uniqBy(modelClasses);
- if (uniqueModelClasses.length !== 1) {
- throw new InvalidReferenceError();
- }
- return modelClasses[0];
- }
- function createGetter(path) {
- if (path.length === 1) {
- const prop = path[0];
- return obj => obj[prop];
- } else {
- return obj => get(obj, path);
- }
- }
- function createSetter(path) {
- if (path.length === 1) {
- const prop = path[0];
- return (obj, value) => (obj[prop] = value);
- } else {
- return (obj, value) => set(obj, path, value);
- }
- }
- module.exports = RelationProperty;
|