diff --git a/__tests__/statements/delete_statement.spec.ts b/__tests__/statements/delete_statement.spec.ts index 948936e..8d5bbe4 100644 --- a/__tests__/statements/delete_statement.spec.ts +++ b/__tests__/statements/delete_statement.spec.ts @@ -1,5 +1,44 @@ -describe('Unit tests for DELETE statement', () => { - test('Delete all from the an index', () => { - expect(true).toBeTruthy(); +import QueryBuilder from '../../src/QueryBuilder'; +import SphinxClient from '../../src/SphinxClient'; +require('iconv-lite').encodingExists('foo'); // fix bug with mysql2 and Jest + +describe('Tests for DELETE statement', () => { + const params = { + host: '127.0.0.1', + port: 9307, + }; + + test('deletes with a where condition', () => { + const conn = new SphinxClient(params); + const compiledQuery = new QueryBuilder(conn) + .delete('rt') + .where('id', '=', 2) + .generate(); + const expectedQuery = `DELETE FROM rt WHERE id = 2`; + + expect(compiledQuery).toBe(expectedQuery); }); -}); \ No newline at end of file + + test('deletes with a match condition', () => { + const conn = new SphinxClient(params); + const compiledQuery = new QueryBuilder(conn) + .delete('rt') + .match(['title', 'content'], 'dinosaur') + .generate(); + const expectedQuery = `DELETE FROM rt WHERE MATCH('(@(title,content) dinosaur)')`; + + expect(compiledQuery).toBe(expectedQuery); + }); + + test('deletes with multiple conditions', () => { + const conn = new SphinxClient(params); + const compiledQuery = new QueryBuilder(conn) + .delete('rt') + .match(['title', 'content'], 'dinosaur') + .where('published_at', '<', 2030) + .generate(); + const expectedQuery = `DELETE FROM rt WHERE MATCH('(@(title,content) dinosaur)') AND published_at < 2030`; + + expect(compiledQuery).toBe(expectedQuery); + }); +}); diff --git a/__tests__/statements/update_statement.spec.ts b/__tests__/statements/update_statement.spec.ts index 55fb818..1b4e0d5 100644 --- a/__tests__/statements/update_statement.spec.ts +++ b/__tests__/statements/update_statement.spec.ts @@ -1,6 +1,5 @@ import QueryBuilder from '../../src/QueryBuilder'; import SphinxClient from '../../src/SphinxClient'; -import Expression from '../../src/Statements/Expression'; require('iconv-lite').encodingExists('foo'); // fix bug with mysql2 and Jest describe('Tests for UPDATE statement', () => { @@ -10,7 +9,7 @@ describe('Tests for UPDATE statement', () => { }; // UPDATE index SET col1 = newval1 [, ...] WHERE where_condition [OPTION opt_name = opt_value [, ...]] - test('create an update statement with a where condition', () => { + test('updates with a where condition', () => { const conn = new SphinxClient(params); const compiledQuery = new QueryBuilder(conn) .update('rt') @@ -22,7 +21,7 @@ describe('Tests for UPDATE statement', () => { expect(compiledQuery).toBe(expectedQuery); }); - test('create an update statement with a match condition', () => { + test('updates with a match condition', () => { const conn = new SphinxClient(params); const compiledQuery = new QueryBuilder(conn) .update('rt') @@ -34,20 +33,20 @@ describe('Tests for UPDATE statement', () => { expect(compiledQuery).toBe(expectedQuery); }); - test('create an update query with multiple conditions', () => { + test('updates with multiple conditions', () => { const conn = new SphinxClient(params); const compiledQuery = new QueryBuilder(conn) .update('rt') - .set({title: 'UPDATE no dinosaurs in 2030!'}) + .set({title: "UPDATE there\'re not dinosaurs in 2030!"}) .match(['title', 'content'], 'dinosaur') .where('published_at', '<', 2030) .generate(); - const expectedQuery = `UPDATE rt SET title='UPDATE no dinosaurs in 2030!' WHERE MATCH('(@(title,content) dinosaur)') AND published_at < 2030`; + const expectedQuery = `UPDATE rt SET title='UPDATE there\\'re not dinosaurs in 2030!' WHERE MATCH('(@(title,content) dinosaur)') AND published_at < 2030`; expect(compiledQuery).toBe(expectedQuery); }); - it('uses OPTION expression for customizing the search', () => { + it('updates with OPTION expression for customizing the search', () => { const conn = new SphinxClient(params); let compiledQuery = new QueryBuilder(conn) .select('id') @@ -58,43 +57,5 @@ describe('Tests for UPDATE statement', () => { let expectedQuery = `SELECT id FROM rt_sales WHERE MATCH('(@product_name "iPhone XS")') OPTION ranker='sph04'`; expect(compiledQuery).toBe(expectedQuery); - - compiledQuery = new QueryBuilder(conn) - .select('id') - .from('rt_sales') - .match('product_name', '"iPhone XS"', false) - .orMatch('*', '"iphone apple"~4', false) - .option('ranker', Expression.raw('sph04')) - .option('field_weights', {product_name: 100}) - .generate(); - expectedQuery = `SELECT id FROM rt_sales WHERE MATCH('(@product_name "iPhone XS") | (@* "iphone apple"~4)') OPTION ranker=sph04,field_weights=(product_name=100)`; - - expect(compiledQuery).toBe(expectedQuery); - - compiledQuery = new QueryBuilder(conn) - .select('id') - .from('rt_sales') - .match('product_name', '"iPhone XS"') - .orMatch('*', '"iphone apple"~4', false) - .limit(5) - .option('ranker', Expression.raw('sph04')) - .option('field_weights', {product_name: 100}) - .generate(); - expectedQuery = `SELECT id FROM rt_sales WHERE MATCH('(@product_name \"iPhone XS\") | (@* "iphone apple"~4)') LIMIT 5 OPTION ranker=sph04,field_weights=(product_name=100)`; - - expect(compiledQuery).toBe(expectedQuery); - - compiledQuery = new QueryBuilder(conn) - .select('id') - .from('rt_sales') - .match('product_name', '"iPhone XS"') - .orMatch('*', '"iphone apple"~4', false) - .limit(5) - .option('ranker', Expression.raw('sph04')) - .option('field_weights', {product_name: 100, other: 1}) - .generate(); - expectedQuery = `SELECT id FROM rt_sales WHERE MATCH('(@product_name \"iPhone XS\") | (@* "iphone apple"~4)') LIMIT 5 OPTION ranker=sph04,field_weights=(product_name=100,other=1)`; - - expect(compiledQuery).toBe(expectedQuery); }); }); diff --git a/src/QueryBuilder.ts b/src/QueryBuilder.ts index 8eaefef..f8b2599 100644 --- a/src/QueryBuilder.ts +++ b/src/QueryBuilder.ts @@ -3,6 +3,7 @@ import InsertStatement from './Statements/InsertReplaceStatement'; import SelectStatement from './Statements/SelectStatement'; import TransactionStatement from './Statements/TransactionStatement'; import UpdateStatement from './Statements/UpdateStatement'; +import DeleteStatement from './Statements/DeleteStatement'; export default class QueryBuilder { // protected type: QueryType; @@ -41,8 +42,8 @@ export default class QueryBuilder { return new UpdateStatement(this.connection, index); } - public delete() { - + public delete(index: string): DeleteStatement { + return new DeleteStatement(this.connection, index); } public transaction(): TransactionStatement { diff --git a/src/QueryType.ts b/src/QueryType.ts deleted file mode 100644 index 0e6e70d..0000000 --- a/src/QueryType.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum QueryType { - Select, - Create, - Update, - Delete, -} \ No newline at end of file diff --git a/src/Statements/DeleteStatement.ts b/src/Statements/DeleteStatement.ts index e69de29..88de69a 100644 --- a/src/Statements/DeleteStatement.ts +++ b/src/Statements/DeleteStatement.ts @@ -0,0 +1,115 @@ +import MatchExprStatement from './statement_expressions/MatchStatement'; +import ClientInterface from '../ClientInterface'; +import WhereStatement from './statement_expressions/WhereStatement'; +import StatementBuilderBase from './StatementBuilderBase'; + +/** + * DELETE FROM index WHERE where_condition + */ +export default class DeleteStatement { + protected connection: ClientInterface; + protected index: string; + protected matchStatement: MatchExprStatement = new MatchExprStatement(); + protected whereConditions: WhereStatement[] = []; + + public constructor(connection: ClientInterface, index: string) { + this.connection = connection; + this.index = index; + } + + /** + * + * @param columnExpr + * @param operator + * @param value + */ + public where(columnExpr: string, operator: string, value?: any): DeleteStatement { + if (value === undefined) { + value = operator; + operator = '='; + } + + const condition = new WhereStatement(columnExpr, operator, value); + this.whereConditions = [...this.whereConditions, condition]; + + return this; + } + + public whereIn(column: string, values: any[]) { + const condition = new WhereStatement(column, 'IN', values); + this.whereConditions = [...this.whereConditions, condition]; + + return this; + } + + public whereNotIn(column: string, values: any[]) { + const condition = new WhereStatement(column, 'NOT IN', values); + this.whereConditions = [...this.whereConditions, condition]; + + return this; + } + + public between(column: string, value1: any, value2: any) { + const condtion = new WhereStatement(column, 'BETWEEN', [value1, value2]); + this.whereConditions = [...this.whereConditions, condtion]; + + 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; + } + + public generate() : string { + let statement = 'DELETE FROM '; + statement += this.index; + + 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 '); + } + + return statement; + } + + public execute() { + const query = this.generate(); + // make query + + return query; + } +}