sphinxql
Version:
SphinxQL query builder for Node.JS. Supports Sphinx search(2.x and 3.x) and Manticore search
283 lines (239 loc) • 9.35 kB
text/typescript
require ('es7-object-polyfill');
import FromExprStatement from './statement_expressions/FromExprStatement';
import HavingExprStatement from './statement_expressions/HavingExprStatement';
import LimitExprStatement from './statement_expressions/LimitExprStatement';
import OrderByExprStatement from './statement_expressions/OrderByExprStatement';
import SelectExprStatement from './statement_expressions/SelectExprStatement';
import MatchExprStatement from './statement_expressions/MatchStatement';
import ClientInterface from '../ClientInterface';
import GroupByExprStatement from './statement_expressions/GroupByExprStatement';
import WhereStatement from './statement_expressions/WhereStatement';
import StatementBuilderBase from './StatementBuilderBase';
import OptionExprStatement from './statement_expressions/OptionExprStatement';
import FacetStatement from './FacetStatement';
import Expression from './Expression';
import BaseStatement from './BaseStatement';
/**
SELECT
select_expr [, select_expr ...]
FROM index [, index2 ...]
[WHERE where_condition]
[GROUP [N] BY {col_name | expr_alias} [, {col_name | expr_alias}]]
[WITHIN GROUP ORDER BY {col_name | expr_alias} {ASC | DESC}]
[HAVING having_condition]
[ORDER BY {col_name | expr_alias} {ASC | DESC} [, ...]]
[LIMIT [offset,] row_count]
[OPTION opt_name = opt_value [, ...]]
[FACET facet_options[ FACET facet_options][ ...]]
*/
export default class SelectStatement extends BaseStatement {
protected select: SelectExprStatement;
protected fromIndexes: FromExprStatement;
protected matchStatement: MatchExprStatement = new MatchExprStatement();
protected whereConditions: WhereStatement[] = [];
protected groupByExpr: GroupByExprStatement[] = [];
protected havingExpr: HavingExprStatement;
protected orderByFields: OrderByExprStatement[] = [];
protected limitExpr: LimitExprStatement;
protected optionExprs: OptionExprStatement[] = [];
protected facetExprs: FacetStatement[] = [];
public constructor(connection: ClientInterface, ...fields: string[]) {
super(connection);
this.select = new SelectExprStatement(...fields);
}
public from(...indexes: any[]) : SelectStatement {
this.fromIndexes = new FromExprStatement(...indexes);
return this;
}
/**
*
* @param columnExpr
* @param operator
* @param value
*/
public where(columnExpr: string | Expression, operator: string, value?: any): SelectStatement {
if (value === undefined) {
value = operator;
operator = '=';
}
let condition: WhereStatement = null;
if (columnExpr instanceof Expression) {
condition = new WhereStatement(columnExpr.getExpression(), operator, value);
} else {
condition = new WhereStatement(columnExpr, operator, value);
}
this.whereConditions = [...this.whereConditions, condition];
return this;
}
public whereIn(column: string | Expression, values: any[]) {
let condition: WhereStatement = null;
if (column instanceof Expression) {
condition = new WhereStatement(column.getExpression(), 'IN', values);
} else {
condition = new WhereStatement(column, 'IN', values);
}
this.whereConditions = [...this.whereConditions, condition];
return this;
}
public whereNotIn(column: string | Expression, values: any[]) {
let condition: WhereStatement = null;
if (column instanceof Expression) {
condition = new WhereStatement(column.getExpression(), 'NOT IN', values);
} else {
condition = new WhereStatement(column, 'NOT IN', values);
}
this.whereConditions = [...this.whereConditions, condition];
return this;
}
public between(column: string | Expression, value1: any, value2: any) {
let condition: WhereStatement = null;
if (column instanceof Expression) {
condition = new WhereStatement(column.getExpression(), 'BETWEEN', [value1, value2]);
} else {
condition = new WhereStatement(column, 'BETWEEN', [value1, value2]);
}
this.whereConditions = [...this.whereConditions, condition];
return this;
}
/**
* Adds an AND logical operator (by default) to the new full text condition.
* It recieves a field or an array of string fields to match against.
* "value" parameter is used for the text to match.
* If escapeValue is true (default) it will escape full text operators
* to prevent security issues, else the value will contain syntax FT operators
* to make possible use proximity, negation, exact phrase, and so forth.
*/
public match(fields: string[] | string, value: string, escapeValue: boolean = true) {
this.matchStatement.match(fields.length ? fields : undefined, value, escapeValue);
return this;
}
/**
* Adds an OR "|" logical operator to the new full text condition.
* It MUST be used after call "match" method because "orMatch" preppends
* the OR operator.
*/
public orMatch(fields: string[] | string, value: string, escapeValue: boolean = true) {
this.matchStatement.orMatch(fields.length ? fields : undefined, value, escapeValue);
return this;
}
/**
* Creates the a GROUP BY expression and append it to the
* end of the array.
*/
public groupBy(columns: string[]) {
const expression = new GroupByExprStatement(columns);
this.groupByExpr = [...this.groupByExpr, expression];
return this;
}
/**
* Creates a HAVING expression.
* Only is allowed one condition in HAVING expression.
*/
public having(columnExpr: string, operator: string, value?: any) {
if (value === undefined) {
value = operator;
operator = '=';
}
this.havingExpr = new HavingExprStatement(columnExpr, operator, value);
return this;
}
/**
* Creates an ORDER BY expression and appends it to the end of columns.
*/
public orderBy(fields: object) {
for (const [field, order] of Object.entries(fields)) {
this.orderByFields = [...this.orderByFields, new OrderByExprStatement(field, order)];
}
return this;
}
/**
* Creates a LIMIT expression if doesn't exist with a default size.
* If "limit" method has been called before then updates the offset.
*/
public offset(offset: number = 0) {
if (this.limitExpr !== undefined) {
this.limitExpr.setOffset(offset);
} else {
this.limitExpr = new LimitExprStatement(offset);
}
return this;
}
/**
* Creates a LIMIT expression if doesn't exist with the specified
* size (length or number of results).
* If "offset" method has been called before then updates the size.
*/
public limit(size: number = 5) {
if (this.limitExpr !== undefined) {
this.limitExpr.setSize(size);
} else {
this.limitExpr = new LimitExprStatement(0, size);
}
return this;
}
/**
* Creates a new option and appends it at the end of OPTION expressions.
* "option" parameter is the name of the option and "value", as you might guest,
* contains all the option values. "value" can be a key/value object, a string
* or a Expression instance object.
*/
public option(option: string, value: any) {
this.optionExprs = [...this.optionExprs, new OptionExprStatement(option, value)];
return this;
}
public facet(cb) {
let values = [];
values = [...this.facetExprs, cb.apply(this, [new FacetStatement(this.connection)])];
this.facetExprs = values;
return this;
}
public generate() : string {
let statement = 'SELECT ';
statement += this.select.build();
statement += ' FROM ';
statement += this.fromIndexes.build();
const hasMatchStatement: boolean = this.matchStatement.getParts().length > 0;
const hasWhereStatements: boolean = this.whereConditions.length > 0;
if (hasWhereStatements || hasMatchStatement) {
statement += ' WHERE ';
if (hasMatchStatement) {
statement += `MATCH(${this.matchStatement.build()})`;
if (hasWhereStatements) {
statement += ' AND ';
}
}
let stringStatements: string[];
stringStatements = this.whereConditions.map((condition: StatementBuilderBase) => condition.build());
statement += stringStatements.join(' AND ');
}
if (this.groupByExpr.length) {
statement += ' GROUP BY ';
let stringStatements: string[];
stringStatements = this.groupByExpr.map((group: GroupByExprStatement) => group.build());
statement += stringStatements.join(', ');
}
if (this.havingExpr) {
statement += ` HAVING ${this.havingExpr.build()}`;
}
if (this.orderByFields.length) {
statement += ' ORDER BY ';
let stringStatements: string[];
stringStatements = this.orderByFields.map((field: StatementBuilderBase) => field.build());
statement += stringStatements.join(', ');
}
if (this.limitExpr !== undefined) {
statement += ` LIMIT ${this.limitExpr.build()}`;
}
if (this.optionExprs.length) {
statement += ` OPTION ${this.optionExprs.map((option) => option.build()).join(',')}`;
}
if (this.facetExprs.length) {
let facetStatement: string = '';
facetStatement = this.facetExprs.map((facet) => {
return ` FACET ${facet.build()}`;
}).join('');
statement += facetStatement;
}
return statement;
}
}