Skip to content

Commit

Permalink
feat(search-service): add option to change source name (#450)
Browse files Browse the repository at this point in the history
  • Loading branch information
akshatdubeysf authored Jan 7, 2022
1 parent fc8386e commit b917898
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 86 deletions.
141 changes: 68 additions & 73 deletions services/search-service/src/classes/base/query.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
protected schema?: string;
protected idType?: string = 'uuid';
protected _placeholderIndex = 0;
protected modelNameMap = new Map<string, string>();
protected get placeholder(): string {
this._placeholderIndex += 1;
return this.paramString(this._placeholderIndex);
Expand All @@ -50,6 +51,25 @@ export abstract class SearchQueryBuilder<T extends Model> {

abstract unionString: string;

build(
models: (SearchableModel<T> | typeof Model)[],
ignoredColumns?: ModelProperties<T>[],
type?: typeof Model,
idType?: string,
) {
this.idType = idType ?? 'uuid';
if (!this.query.match) {
throw new HttpErrors.BadRequest(Errors.MISSING_MATCH);
}
if (!(models && models.length > 0)) {
throw new HttpErrors.BadRequest(Errors.NO_COLUMNS_TO_MATCH);
}
return {
query: this.queryBuild(models, ignoredColumns, type),
params: this.paramsBuild(this.query.match),
};
}

limit() {
if (this.query.limit) {
if (this.query.limitByType) {
Expand Down Expand Up @@ -111,12 +131,13 @@ export abstract class SearchQueryBuilder<T extends Model> {
filter: (keyof T)[],
) {
if (Array.isArray(columns)) {
return this._getColumnListFromArray(model, columns, filter);
return this.getColumnListFromArray(model, columns, filter);
} else {
return this._getColumnListFromMap(model, columns, filter);
return this.getColumnListFromMap(model, columns, filter);
}
}
_getColumnListFromMap(

getColumnListFromMap(
model: typeof Model,
columns: ColumnMap<T>,
filter: (keyof T)[],
Expand All @@ -134,7 +155,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
if (columnNameInMap === IGNORED_COLUMN) {
return `null as ${column}`;
} else {
const dbName = this._getColumnName(model, columnNameInMap);
const dbName = this.getColumnName(model, columnNameInMap);
return this._formatColumnSameInDb(column as keyof T, dbName);
}
})
Expand All @@ -145,18 +166,18 @@ export abstract class SearchQueryBuilder<T extends Model> {
};
}

_getColumnListFromArray(
getColumnListFromArray(
model: typeof Model,
columns: Array<keyof T>,
filter: (keyof T)[],
) {
const columnList = columns
.filter(column => !filter.includes(column))
.map(column => this._getColumnName(model, column))
.map(column => this.getColumnName(model, column))
.join(', ');
const selectors = columns
.map(column => {
const nameInDb = this._getColumnName(model, column);
const nameInDb = this.getColumnName(model, column);
return this._formatColumnSameInDb(column, nameInDb);
})
.join(', ');
Expand All @@ -166,25 +187,6 @@ export abstract class SearchQueryBuilder<T extends Model> {
};
}

build(
models: (SearchableModel<T> | typeof Model)[],
ignoredColumns?: ModelProperties<T>[],
type?: typeof Model,
idType?: string,
) {
this.idType = idType ?? 'uuid';
if (!this.query.match) {
throw new HttpErrors.BadRequest(Errors.MISSING_MATCH);
}
if (!(models && models.length > 0)) {
throw new HttpErrors.BadRequest(Errors.NO_COLUMNS_TO_MATCH);
}
return {
query: this.queryBuild(models, ignoredColumns, type),
params: this.paramsBuild(this.query.match),
};
}

paramsBuild(param: string): AnyObject | Array<AnyObject | string> {
const params = this.baseQueryList.reduce(
(t: ShortHandEqualType[], v) => [...t, ...v.params],
Expand Down Expand Up @@ -220,6 +222,9 @@ export abstract class SearchQueryBuilder<T extends Model> {
}
return combined;
}, model.columns);
if (model.identifier) {
this.modelNameMap.set(model.model.name, model.identifier);
}
this.search(model.model, mapWithDefaults, skipped);
} else {
this.search(model, defaultColumns, skipped);
Expand Down Expand Up @@ -270,7 +275,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
const expression = where[key] as
| PredicateComparison<typeof key>
| (typeof key & ShortHandEqualType);
const columnName = this._getColumnName(model, key);
const columnName = this.getColumnName(model, key);
if (expression === null || expression === undefined) {
stmts.push({sql: `${columnName} IS NULL`, params: []});
} else if (typeof expression === 'object') {
Expand All @@ -281,7 +286,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
return;
}
} else {
columnValue = this._toColumnValue(p, expression);
columnValue = this.toColumnValue(p, expression);
if (columnValue === null) {
stmts.push({sql: `${columnName} IS NULL`, params: []});
} else {
Expand Down Expand Up @@ -337,7 +342,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
const expressionValue = expression[
operator
] as PredicateValueType<ShortHandEqualType>;
const columnValue = this._buildColumnValueForExpression(expressionValue, p);
const columnValue = this.buildColumnValueForExpression(expressionValue, p);
if (['inq', 'nin', 'between'].includes(operator)) {
if (operator === 'between' && columnValue.length !== TWO) {
throw new HttpErrors.BadRequest(Errors.EXACTLY_TWO_VALUES_FOR_BETWEEN);
Expand All @@ -347,20 +352,20 @@ export abstract class SearchQueryBuilder<T extends Model> {
}
}
}
return this._buildExpression(key, p, operator, columnValue, model);
return this.buildExpression(key, p, operator, columnValue, model);
}

_buildColumnValueForExpression(
buildColumnValueForExpression(
expressionValue: PredicateValueType<ShortHandEqualType>,
p: PropertyDefinition,
) {
const columnValue = [];
if (Array.isArray(expressionValue)) {
for (let j = 0, m = expressionValue.length; j < m; j++) {
columnValue.push(this._toColumnValue(p, expressionValue[j]));
columnValue.push(this.toColumnValue(p, expressionValue[j]));
}
} else {
columnValue.push(this._toColumnValue(p, expressionValue));
columnValue.push(this.toColumnValue(p, expressionValue));
}
return columnValue;
}
Expand All @@ -373,14 +378,14 @@ export abstract class SearchQueryBuilder<T extends Model> {
}
}

_getColumnName(model: typeof Model, name: keyof T) {
getColumnName(model: typeof Model, name: keyof T) {
if (model.definition.properties[name as string]) {
return model.definition.properties[name as string].name || name;
}
return undefined;
}

_toColumnValue(
toColumnValue(
prop: PropertyDefinition,
val: PredicateValueType<ShortHandEqualType>,
) {
Expand All @@ -400,7 +405,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
(prop.type as Type<string>).name === 'Timestamp') &&
(val instanceof Date || typeof val === 'string')
) {
return this._toDateType(val);
return this.toDateType(val);
}

// PostgreSQL support char(1) Y/N
Expand All @@ -410,13 +415,13 @@ export abstract class SearchQueryBuilder<T extends Model> {

if (Array.isArray(prop.type)) {
// There is two possible cases for the type of "val" as well as two cases for dataType
return this._toArrayPropTypes(prop, val);
return this.toArrayPropTypes(prop, val);
}

return val;
}

_toDateType(val: Date | string) {
toDateType(val: Date | string) {
if (!(val instanceof Date) || !val.toISOString) {
val = new Date(val);
}
Expand All @@ -429,7 +434,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
};
}

_toArrayPropTypes<R>(prop: PropertyDefinition, val: R[] | R) {
toArrayPropTypes<R>(prop: PropertyDefinition, val: R[] | R) {
const isArrayDataType =
prop.postgresql && prop.postgresql.dataType === 'varchar[]';
if (Array.isArray(val)) {
Expand All @@ -447,20 +452,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
}
}

_escape(str: string) {
let escaped = '"';
for (const c of str) {
if (c === '"') {
escaped += c + c;
} else {
escaped += c;
}
}
escaped += '"';
return escaped;
}

_buildExpression(
buildExpression(
columnName: keyof T,
prop: PropertyDefinition,
operator: string,
Expand All @@ -469,36 +461,36 @@ export abstract class SearchQueryBuilder<T extends Model> {
) {
const getPlaceholder = () => this.parseIdPlaceholder(prop);

let expression = this._getColumnName(model, columnName);
let expression = this.getColumnName(model, columnName);
let clause: Query;
switch (operator) {
case 'gt':
expression += '>';
clause = this._buildClauseFromExpress(value, '', false, getPlaceholder);
clause = this.buildClauseFromExpress(value, '', false, getPlaceholder);
break;
case 'gte':
expression += '>=';
clause = this._buildClauseFromExpress(value, '', false, getPlaceholder);
clause = this.buildClauseFromExpress(value, '', false, getPlaceholder);
break;
case 'lt':
expression += '<';
clause = this._buildClauseFromExpress(value, '', false, getPlaceholder);
clause = this.buildClauseFromExpress(value, '', false, getPlaceholder);
break;
case 'lte':
expression += '<=';
clause = this._buildClauseFromExpress(value, '', false, getPlaceholder);
clause = this.buildClauseFromExpress(value, '', false, getPlaceholder);
break;
case 'neq':
if (value === null) {
expression += ' IS NOT NULL';
} else {
expression += '!=';
}
clause = this._buildClauseFromExpress(value, '', false, getPlaceholder);
clause = this.buildClauseFromExpress(value, '', false, getPlaceholder);
break;
case 'between':
expression += ' BETWEEN ';
clause = this._buildClauseFromExpress(
clause = this.buildClauseFromExpress(
value,
' AND ',
false,
Expand All @@ -507,21 +499,11 @@ export abstract class SearchQueryBuilder<T extends Model> {
break;
case 'nin':
expression += ' NOT IN ';
clause = this._buildClauseFromExpress(
value,
', ',
true,
getPlaceholder,
);
clause = this.buildClauseFromExpress(value, ', ', true, getPlaceholder);
break;
case 'inq':
expression += ' IN ';
clause = this._buildClauseFromExpress(
value,
', ',
true,
getPlaceholder,
);
clause = this.buildClauseFromExpress(value, ', ', true, getPlaceholder);
break;
default:
throw new HttpErrors.BadRequest(
Expand All @@ -534,7 +516,7 @@ export abstract class SearchQueryBuilder<T extends Model> {
};
}

_buildClauseFromExpress(
buildClauseFromExpress(
values: (Query | ShortHandEqualType)[] | ShortHandEqualType | Query,
separator: string,
grouping: boolean,
Expand Down Expand Up @@ -572,6 +554,19 @@ export abstract class SearchQueryBuilder<T extends Model> {
}
}

getTableName(model: typeof Model) {
return model.modelName;
}

getSchemaName(model: typeof Model) {
return this.schema ?? `public`;
}

getModelName(model: typeof Model) {
const mapName = this.modelNameMap.get(model.name);
return mapName ?? model.modelName ?? model.name;
}

private _isQuery(query: Query | ShortHandEqualType): query is Query {
return !!(query && (query as Query).sql && (query as Query).params);
}
Expand Down
17 changes: 14 additions & 3 deletions services/search-service/src/classes/mysql/query.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ export class MySqlQueryBuilder<T extends Model> extends SearchQueryBuilder<T> {
columns: Array<keyof T> | ColumnMap<T>,
ignoredColumns: (keyof T)[],
) {
const modelName = model.modelName;
const tableName = this.getTableName(model);
const sourceName = this.getModelName(model);
const {columnList, selectors} = this.getColumnListFromArrayOrMap(
model,
columns,
ignoredColumns,
);
const where = this.whereBuild(model, this.query.where?.[modelName]);
const where = this.whereBuild(model, this.query.where?.[sourceName]);
const whereClause = [`MATCH (${columnList}) AGAINST (':0')`];
if (where.sql) {
whereClause.push(where.sql);
}
const query = `SELECT ${selectors}, '${modelName}' as source, MATCH (${columnList}) AGAINST (':0') AS rank from ${modelName} where ${whereClause.join(
const query = `SELECT ${selectors}, '${sourceName}' as source, MATCH (${columnList}) AGAINST (':0') AS rank from ${tableName} where ${whereClause.join(
' AND ',
)}`;

Expand All @@ -40,4 +41,14 @@ export class MySqlQueryBuilder<T extends Model> extends SearchQueryBuilder<T> {
paramString(index: number) {
return `$${index - 1}`;
}

getTableName(model: typeof Model) {
const mappedName = this.modelNameMap.get(model.name);
return (
mappedName ??
model.definition?.settings?.mysql?.table ??
model.modelName ??
model.name.toLowerCase()
);
}
}
Loading

0 comments on commit b917898

Please sign in to comment.