ts-sql-query
Version:
Type-safe SQL query builder like QueryDSL or JOOQ in Java or Linq in .Net for TypeScript with MariaDB, MySql, Oracle, PostgreSql, Sqlite and SqlServer support.
780 lines (779 loc) • 30.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__setQueryMetadata = exports.ComposeSplitQueryBuilder = void 0;
const values_1 = require("../expressions/values");
const SqlBuilder_1 = require("../sqlBuilders/SqlBuilder");
const attachSource_1 = require("../utils/attachSource");
const ITableOrView_1 = require("../utils/ITableOrView");
class ComposeSplitQueryBuilder {
constructor(sqlBuilder) {
this.__compositions = [];
this.__sqlBuilder = sqlBuilder;
}
__isValue(value) {
return this.__sqlBuilder._isValue(value);
}
compose(config) {
this.__lastComposition = {
type: 'compose',
config: config,
deleteExternal: false,
deleteInternal: false
};
return this;
}
composeDeletingInternalProperty(config) {
this.__lastComposition = {
type: 'compose',
config: config,
deleteExternal: false,
deleteInternal: true
};
return this;
}
composeDeletingExternalProperty(config) {
this.__lastComposition = {
type: 'compose',
config: config,
deleteExternal: true,
deleteInternal: false
};
return this;
}
withNoneOrOne(fn) {
const last = this.__lastComposition;
if (!last) {
throw new Error('Illegal state');
}
this.__lastComposition = undefined;
last.fn = fn;
last.cardinality = 'noneOrOne';
this.__compositions.push(last);
return this;
}
withOne(fn) {
const last = this.__lastComposition;
if (!last) {
throw new Error('Illegal state');
}
this.__lastComposition = undefined;
last.fn = fn;
last.cardinality = 'one';
this.__compositions.push(last);
return this;
}
withMany(fn) {
const last = this.__lastComposition;
if (!last) {
throw new Error('Illegal state');
}
this.__lastComposition = undefined;
last.fn = fn;
last.cardinality = 'many';
this.__compositions.push(last);
return this;
}
withOptionalMany(fn) {
const last = this.__lastComposition;
if (!last) {
throw new Error('Illegal state');
}
this.__lastComposition = undefined;
last.fn = fn;
last.cardinality = 'optionalMany';
this.__compositions.push(last);
return this;
}
splitRequired(propertyName, mappig) {
const split = {
type: 'split',
optional: false,
propertyName: propertyName,
mapping: mappig
};
this.__compositions.push(split);
return this;
}
splitOptional(propertyName, mappig) {
const split = {
type: 'split',
optional: true,
propertyName: propertyName,
mapping: mappig
};
this.__compositions.push(split);
return this;
}
split(propertyName, mappig) {
const split = {
type: 'split',
optional: true,
propertyName: propertyName,
mapping: mappig
};
this.__compositions.push(split);
return this;
}
guidedSplitRequired(propertyName, mappig) {
const split = {
type: 'guidedSplit',
optional: false,
propertyName: propertyName,
mapping: mappig
};
this.__compositions.push(split);
return this;
}
guidedSplitOptional(propertyName, mappig) {
const split = {
type: 'guidedSplit',
optional: true,
propertyName: propertyName,
mapping: mappig
};
this.__compositions.push(split);
return this;
}
__applyCompositions(result, source) {
const compositions = this.__compositions;
if (compositions.length <= 0) {
return result;
}
for (let i = 0, length = compositions.length; i < length; i++) {
const composition = compositions[i];
result = result.then(dataResult => {
try {
if (composition.type === 'split') {
return this.__applySplit(dataResult, composition);
}
if (composition.type === 'compose') {
return this.__applyComposition(dataResult, composition, source);
}
else {
return this.__applyGuidedSplit(dataResult, composition);
}
}
catch (e) {
throw (0, attachSource_1.attachSource)(e, source);
}
});
}
return result;
}
__applySplit(dataResult, split) {
let dataList;
if (!dataResult) {
return dataResult;
}
else if (Array.isArray(dataResult)) {
dataList = dataResult;
}
else {
dataList = [dataResult];
}
const mapping = split.mapping;
const optional = split.optional;
const propertyName = split.propertyName;
for (let i = 0, length = dataList.length; i < length; i++) {
const external = dataList[i];
const result = {};
let hasContent = false;
for (let prop in mapping) {
const externalProp = mapping[prop];
const value = external[externalProp];
if (value !== null && value !== undefined) {
result[prop] = value;
hasContent = true;
}
delete external[externalProp];
}
if (propertyName in external) {
throw new Error('The property ' + propertyName + ' already exists in the result of the query');
}
if (optional) {
if (hasContent) {
external[propertyName] = result;
}
}
else {
external[propertyName] = result;
}
}
return dataResult;
}
__applyComposition(dataResult, composition, source) {
const config = composition.config;
const resultProperty = config.propertyName;
const externalProperty = config.externalProperty;
if (!this.__columns) {
return dataResult;
}
if (!(externalProperty in this.__columns)) {
// this is the case of a select picking columns and the externalProperty was not picked
return dataResult;
}
let dataList;
if (!dataResult) {
return dataResult;
}
else if (Array.isArray(dataResult)) {
dataList = dataResult;
}
else {
dataList = [dataResult];
}
const dataMap = {};
const ids = [];
for (let i = 0, length = dataList.length; i < length; i++) {
const data = dataList[i];
const externalValue = data[externalProperty];
dataMap[externalValue] = data;
if (externalValue !== null || externalValue !== undefined) {
ids.push(data[externalProperty]);
}
if (composition.deleteExternal) {
delete data[externalProperty];
}
if (resultProperty in data) {
throw new Error('The property ' + resultProperty + ' already exists in the result of the external query');
}
}
const fn = composition.fn;
if (!fn) {
throw new Error('Illegal state');
}
if (ids.length <= 0) {
return dataResult;
}
return fn(ids).then((internalList) => {
try {
this.__processCompositionResult(internalList, dataList, dataMap, composition);
}
catch (e) {
throw (0, attachSource_1.attachSource)(e, source);
}
return dataResult;
});
}
__applyGuidedSplit(dataResult, split) {
let dataList;
if (!dataResult) {
return dataResult;
}
else if (Array.isArray(dataResult)) {
dataList = dataResult;
}
else {
dataList = [dataResult];
}
const mapping = split.mapping;
const optional = split.optional;
const propertyName = split.propertyName;
for (let i = 0, length = dataList.length; i < length; i++) {
const forcedAsMandatoryProperties = [];
const forcedAsMandatoryMapping = [];
const external = dataList[i];
const result = {};
let hasContent = false;
for (let prop in mapping) {
let externalProp = mapping[prop];
if (externalProp.endsWith('!')) {
externalProp = externalProp.substring(0, externalProp.length - 1);
forcedAsMandatoryProperties.push(externalProp);
forcedAsMandatoryMapping.push(prop);
}
else if (externalProp.endsWith('?')) {
externalProp = externalProp.substring(0, externalProp.length - 1);
}
const value = external[externalProp];
if (value !== null && value !== undefined) {
result[prop] = value;
hasContent = true;
}
delete external[externalProp];
}
if (propertyName in external) {
throw new Error('The property ' + propertyName + ' already exists in the result of the query');
}
if (hasContent || !optional) {
for (let j = 0, length2 = forcedAsMandatoryProperties.length; j < length2; j++) {
const externalProp = forcedAsMandatoryProperties[j];
const prop = forcedAsMandatoryMapping[j];
const value = result[prop];
if (value === null || value === undefined) {
let errorMessage = 'Expected a value as result of the column `' + externalProp + '` mapped as `' + prop + '` in a `';
if (optional) {
errorMessage += 'guidedSplitOptional';
}
else {
errorMessage += 'guidedSplitRequired';
}
errorMessage += '` at index `' + i + '`, but null or undefined value was found';
throw new Error(errorMessage);
}
}
}
if (optional) {
if (hasContent) {
external[propertyName] = result;
}
}
else {
external[propertyName] = result;
}
}
return dataResult;
}
__processCompositionResult(internalList, dataList, dataMap, composition) {
const config = composition.config;
const resultProperty = config.propertyName;
const internalProperty = config.internalProperty;
const cardinality = composition.cardinality;
if (!cardinality) {
throw new Error('Illegal state');
}
if (cardinality === 'one') {
if (dataList.length !== internalList.length) {
throw new Error('The internal query in a query composition returned ' + internalList.length + ' rows when ' + dataList.length + ' was expected');
}
}
else if (cardinality === 'many' || cardinality === 'optionalMany') {
for (let i = 0, length = dataList.length; i < length; i++) {
const data = dataList[i];
data[resultProperty] = [];
}
}
for (let i = 0, length = internalList.length; i < length; i++) {
const internalData = internalList[i];
const internalValue = internalData[internalProperty];
if (composition.deleteInternal) {
delete internalData[internalProperty];
}
const data = dataMap[internalValue];
if (!data) {
throw new Error('The internal query in a query composition returned an element identified with ' + internalValue + ' that is not pressent in the external query result');
}
if (cardinality === 'many' || cardinality === 'optionalMany') {
const resultList = data[resultProperty];
resultList.push(internalData);
}
else if (cardinality === 'noneOrOne' || cardinality === 'one') {
const previous = data[resultProperty];
if (previous) {
throw new Error('The internal query in a query composition retunrned multiple elements for an element identified with ' + internalValue);
}
data[resultProperty] = internalData;
}
else {
throw new Error('Illegal state');
}
}
if (cardinality === 'optionalMany') {
for (let i = 0, length = dataList.length; i < length; i++) {
const data = dataList[i];
if (data[resultProperty].length <= 0) {
delete data[resultProperty];
}
}
}
}
__transformValueFromDB(valueSource, value, column, index, count) {
const valueSourcePrivate = (0, values_1.__getValueSourcePrivate)(valueSource);
const typeAdapter = valueSourcePrivate.__typeAdapter;
let result;
if (typeAdapter) {
result = typeAdapter.transformValueFromDB(value, valueSourcePrivate.__valueTypeName, this.__sqlBuilder._defaultTypeAdapter);
}
else {
result = this.__sqlBuilder._defaultTypeAdapter.transformValueFromDB(value, valueSourcePrivate.__valueTypeName);
}
if (result !== null && result !== undefined) {
return result;
}
if (valueSourcePrivate.__optionalType === 'required') {
let errorMessage = 'Expected a value as result';
if (column !== undefined) {
errorMessage += ' of the column `' + column + '`';
}
if (index !== undefined) {
errorMessage += ' at index `' + index + '`';
}
if (count) {
errorMessage += ' of the count number of rows query';
}
errorMessage += ', but null or undefined value was found';
throw new Error(errorMessage);
}
return result;
}
__transformRow(row, index) {
const columns = this.__columns;
return this.__transformRootObject(!!this.__projectOptionalValuesAsNullable, '', columns, row, index);
}
__transformRootObject(projectOptionalValuesAsNullable, errorPrefix, columns, row, index) {
const result = {};
for (let prop in columns) {
const valueSource = columns[prop];
let value = row[prop];
let transformed;
if ((0, values_1.isValueSource)(valueSource)) {
const valueSourcePrivate = (0, values_1.__getValueSourcePrivate)(valueSource);
if (valueSourcePrivate.__aggregatedArrayColumns) {
transformed = this.__transformAggregatedArray(!!valueSourcePrivate.__aggreagtedProjectingOptionalValuesAsNullable, errorPrefix + prop, valueSource, value, index);
}
else {
transformed = this.__transformValueFromDB(valueSource, value, errorPrefix + prop, index);
}
}
else {
transformed = this.__transformProjectedObject(projectOptionalValuesAsNullable, errorPrefix + prop + '.', prop + '.', valueSource, row, index);
}
if (transformed !== undefined && transformed !== null) {
result[prop] = transformed;
}
else if (projectOptionalValuesAsNullable) {
result[prop] = null;
}
}
return result;
}
__transformAggregatedArray(projectOptionalValuesAsNullable, errorPrefix, valueSource, value, index) {
const valueSourcePrivate = (0, values_1.__getValueSourcePrivate)(valueSource);
if (value === null || value === undefined) {
if (valueSourcePrivate.__optionalType === 'required') {
return [];
}
else {
return null;
}
}
let json = value;
if (typeof value === 'string') {
try {
json = JSON.parse(value);
}
catch (e) {
let errorMessage = 'Invalid JSON string coming from the database for the column `' + errorPrefix + '`';
if (index !== undefined) {
errorMessage += ' at index `' + index + '`';
}
errorMessage += '. ' + e + '. JSON: ' + value;
throw new Error(errorMessage);
}
}
if (json === null || json === undefined) {
if (valueSourcePrivate.__optionalType === 'required') {
return [];
}
else {
return null;
}
}
if (!Array.isArray(json)) {
let errorMessage = 'Invalid JSON string coming from the database for the column `' + errorPrefix + '`';
if (index !== undefined) {
errorMessage += ' at index `' + index + '`';
}
errorMessage += '. An array were expected';
throw new Error(errorMessage);
}
const columns = valueSourcePrivate.__aggregatedArrayColumns;
const result = [];
if ((0, values_1.isValueSource)(columns)) {
const columnPrivate = (0, values_1.__getValueSourcePrivate)(columns);
if (columnPrivate.__aggregatedArrayColumns) {
for (let i = 0, lenght = json.length; i < lenght; i++) {
const resultValue = this.__transformAggregatedArray(!!columnPrivate.__aggreagtedProjectingOptionalValuesAsNullable, errorPrefix + '[' + i + ']', columns, json[i], index);
if (resultValue === null || resultValue === undefined) {
continue;
}
result.push(resultValue);
}
}
else {
for (let i = 0, lenght = json.length; i < lenght; i++) {
const resultValue = this.__transformValueFromDB(columns, json[i], errorPrefix + '[' + i + ']', index);
if (resultValue === null || resultValue === undefined) {
continue;
}
result.push(resultValue);
}
}
}
else if (valueSourcePrivate.__aggregatedArrayMode === 'ResultObject') {
for (let i = 0, lenght = json.length; i < lenght; i++) {
let row = json[i];
const resultObject = this.__transformRootObject(!!valueSourcePrivate.__aggreagtedProjectingOptionalValuesAsNullable, errorPrefix + '[' + i + '].', columns, row, index);
if (resultObject === null || resultObject === undefined) {
continue;
}
result.push(resultObject);
}
}
else {
for (let i = 0, lenght = json.length; i < lenght; i++) {
const row = json[i];
const resultObject = this.__transformProjectedObject(projectOptionalValuesAsNullable, errorPrefix + '[' + i + '].', '', columns, row, index);
if (resultObject === null || resultObject === undefined) {
continue;
}
result.push(resultObject);
}
}
if (result.length <= 0) {
if (valueSourcePrivate.__optionalType === 'required') {
return [];
}
else {
return null;
}
}
return result;
}
__transformProjectedObject(projectOptionalValuesAsNullable, errorPrefix, pathPrefix, columns, row, index) {
const result = {};
let keepObject = false;
// Rule 1
let containsRequiredInOptionalObject = false;
let requiredInOptionalObjectHaveValue = true;
// Rule 2
let firstRequiredTables = new Set();
let alwaysSameRequiredTablesSize = undefined;
let originallyRequiredHaveValue = true;
for (let prop in columns) {
const valueSource = columns[prop];
const propName = pathPrefix + prop;
let value;
if (propName in row) {
value = row[propName];
}
else {
value = row;
const parts = propName.split('.');
for (let i = 0, length = parts.length; i < length; i++) {
if (value) {
value = value[parts[i]];
}
}
}
let transformed;
if ((0, values_1.isValueSource)(valueSource)) {
const valueSourcePrivate = (0, values_1.__getValueSourcePrivate)(valueSource);
if (valueSourcePrivate.__aggregatedArrayColumns) {
transformed = this.__transformAggregatedArray(!!valueSourcePrivate.__aggreagtedProjectingOptionalValuesAsNullable, propName, valueSource, value, index);
}
else {
transformed = this.__transformValueFromDB(valueSource, value, errorPrefix + propName, index);
}
if (valueSourcePrivate.__optionalType === 'requiredInOptionalObject') {
// For rule 1
containsRequiredInOptionalObject = true;
if (transformed === undefined || transformed === null) {
requiredInOptionalObjectHaveValue = false;
}
}
else if (valueSourcePrivate.__optionalType === 'originallyRequired') {
// For rule 2
if (transformed === undefined || transformed === null) {
originallyRequiredHaveValue = false;
}
}
// For rule 2
if (alwaysSameRequiredTablesSize === undefined) {
valueSourcePrivate.__registerTableOrView(this.__sqlBuilder, firstRequiredTables);
alwaysSameRequiredTablesSize = true;
}
else if (alwaysSameRequiredTablesSize) {
let valueSourceRequiredTables = new Set();
valueSourcePrivate.__registerTableOrView(this.__sqlBuilder, valueSourceRequiredTables);
const initialSize = firstRequiredTables.size;
if (initialSize !== valueSourceRequiredTables.size) {
alwaysSameRequiredTablesSize = false;
}
else {
valueSourceRequiredTables.forEach(table => {
firstRequiredTables.add(table);
});
if (initialSize !== firstRequiredTables.size) {
alwaysSameRequiredTablesSize = false;
}
}
}
}
else {
transformed = this.__transformProjectedObject(projectOptionalValuesAsNullable, errorPrefix, propName + '.', valueSource, row, index);
}
if (transformed !== undefined && transformed !== null) {
keepObject = true;
result[prop] = transformed;
}
else if (projectOptionalValuesAsNullable) {
result[prop] = null;
}
}
// General rule
if (!keepObject) {
return undefined;
}
// Rule 1
if (containsRequiredInOptionalObject && !requiredInOptionalObjectHaveValue) {
return undefined;
}
// Rule 2
let onlyOuterJoin = true;
firstRequiredTables.forEach(table => {
if (!(0, ITableOrView_1.__getTableOrViewPrivate)(table).__forUseInLeftJoin) {
onlyOuterJoin = false;
}
});
if (firstRequiredTables.size <= 0) {
onlyOuterJoin = false;
}
if (alwaysSameRequiredTablesSize && onlyOuterJoin && !originallyRequiredHaveValue) {
return undefined;
}
// No need to verify rule 3 due if there is no value an error is thrown
// Rule 4 covered in the general rule
return result;
}
__getOldValueOfColumns(columns) {
for (const property in columns) {
const column = columns[property];
if ((0, values_1.isValueSource)(column)) {
const oldValues = (0, values_1.__getValueSourcePrivate)(column).__getOldValues(this.__sqlBuilder);
if (oldValues) {
return oldValues;
}
}
else {
const oldValues = this.__getOldValueOfColumns(column);
if (oldValues) {
return oldValues;
}
}
}
return undefined;
}
__getValuesForInsertOfColumns(columns) {
for (const property in columns) {
const column = columns[property];
if ((0, values_1.isValueSource)(column)) {
const oldValues = (0, values_1.__getValueSourcePrivate)(column).__getValuesForInsert(this.__sqlBuilder);
if (oldValues) {
return oldValues;
}
}
}
return undefined;
}
__registerTableOrViewOfColumns(columns, requiredTablesOrViews) {
for (const property in columns) {
const column = columns[property];
if ((0, values_1.isValueSource)(column)) {
(0, values_1.__getValueSourcePrivate)(column).__registerTableOrView(this.__sqlBuilder, requiredTablesOrViews);
}
else {
this.__registerTableOrViewOfColumns(column, requiredTablesOrViews);
}
}
}
__registerTableOrViewWithOfColumns(columns, withs) {
for (const property in columns) {
const column = columns[property];
if ((0, values_1.isValueSource)(column)) {
(0, values_1.__getValueSourcePrivate)(column).__addWiths(this.__sqlBuilder, withs);
}
else {
this.__registerTableOrViewWithOfColumns(column, withs);
}
}
}
__registerRequiredColumnOfColmns(columns, requiredColumns, newOnly) {
for (const property in columns) {
const column = columns[property];
if ((0, values_1.isValueSource)(column)) {
(0, values_1.__getValueSourcePrivate)(column).__registerRequiredColumn(this.__sqlBuilder, requiredColumns, newOnly);
}
else {
this.__registerRequiredColumnOfColmns(column, requiredColumns, newOnly);
}
}
}
__getColumnFromColumnsObject(prop) {
return (0, SqlBuilder_1.getQueryColumn)(this.__columns, prop);
}
__getColumnNameFromColumnsObjectLowerCase(prop) {
const columns = this.__columns;
const propName = prop.toLowerCase();
let map = {};
for (const property in columns) {
map[property.toLowerCase()] = property;
}
let realProp = map[propName];
if (realProp) {
let valueSource = columns[realProp];
if ((0, values_1.isValueSource)(valueSource)) {
return realProp;
}
else {
return undefined;
}
}
const route = propName.split('.');
let valueSource = columns;
let path = '';
for (let i = 0, length = route.length; valueSource && i < length; i++) {
const currentProp = route[i];
realProp = map[currentProp];
if (!realProp) {
return undefined;
}
valueSource = valueSource[realProp];
if (path) {
path = path + '.' + realProp;
}
else {
path = realProp;
}
if ((0, values_1.isValueSource)(valueSource)) {
if (i === length - 1) {
return path;
}
else {
return undefined;
}
}
map = {};
for (const property in valueSource) {
map[property.toLowerCase()] = property;
}
}
return undefined;
}
}
exports.ComposeSplitQueryBuilder = ComposeSplitQueryBuilder;
function __setQueryMetadata(source, params, queryMetadata, isSelectPageCountQuery) {
Object.defineProperty(params, '$source', {
value: source,
writable: true,
enumerable: false,
configurable: true
});
if (queryMetadata) {
Object.defineProperty(params, '$metadata', {
value: queryMetadata,
writable: true,
enumerable: false,
configurable: true
});
}
if (isSelectPageCountQuery) {
Object.defineProperty(params, '$isSelectPageCountQuery', {
value: true,
writable: true,
enumerable: false,
configurable: true
});
}
}
exports.__setQueryMetadata = __setQueryMetadata;