From b4c4a64c14b94bc397e0ca7e0c6894aa5d803d75 Mon Sep 17 00:00:00 2001 From: "joao.duarte" Date: Thu, 22 Nov 2018 11:57:26 -0200 Subject: [PATCH 1/3] Added RQL Parser for PostgreSQL --- sources/MVCFramework.RQL.AST2PostgreSQL.pas | 213 ++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 sources/MVCFramework.RQL.AST2PostgreSQL.pas diff --git a/sources/MVCFramework.RQL.AST2PostgreSQL.pas b/sources/MVCFramework.RQL.AST2PostgreSQL.pas new file mode 100644 index 00000000..0d60acb4 --- /dev/null +++ b/sources/MVCFramework.RQL.AST2PostgreSQL.pas @@ -0,0 +1,213 @@ +// *************************************************************************** } +// +// Delphi MVC Framework +// +// Copyright (c) 2010-2018 Daniele Teti and the DMVCFramework Team +// +// https://github.com/danieleteti/delphimvcframework +// +// *************************************************************************** +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// *************************************************************************** + +unit MVCFramework.RQL.AST2PostgreSQL; + +interface + +uses + MVCFramework.RQL.Parser; + +type + TRQLPostgreSQLCompiler = class(TRQLCompiler) + private + function RQLFilterToSQL(const aRQLFIlter: TRQLFilter): string; + function RQLSortToSQL(const aRQLSort: TRQLSort): string; + function RQLLimitToSQL(const aRQLLimit: TRQLLimit): string; + function RQLWhereToSQL(const aRQLWhere: TRQLWhere): string; + function RQLLogicOperatorToSQL(const aRQLFIlter: TRQLLogicOperator): string; + function RQLCustom2SQL(const aRQLCustom: TRQLCustom): string; + public + procedure AST2SQL(const aRQLAST: TRQLAbstractSyntaxTree; out aSQL: string); override; + end; + +implementation + +uses + System.SysUtils, + MVCFramework.RQL.AST2FirebirdSQL; + +{ TRQLPostgreSQLCompiler } + +function TRQLPostgreSQLCompiler.RQLCustom2SQL( + const aRQLCustom: TRQLCustom): string; +begin + if aRQLCustom is TRQLFilter then + begin + Result := RQLFilterToSQL(TRQLFilter(aRQLCustom)); + end + else if aRQLCustom is TRQLLogicOperator then + begin + Result := RQLLogicOperatorToSQL(TRQLLogicOperator(aRQLCustom)); + end + else if aRQLCustom is TRQLSort then + begin + Result := RQLSortToSQL(TRQLSort(aRQLCustom)); + end + else if aRQLCustom is TRQLLimit then + begin + Result := RQLLimitToSQL(TRQLLimit(aRQLCustom)); + end + else if aRQLCustom is TRQLWhere then + begin + Result := RQLWhereToSQL(TRQLWhere(aRQLCustom)); + end + else + raise ERQLException.CreateFmt('Unknown token in compiler: %s', [aRQLCustom.ClassName]); +end; + +function TRQLPostgreSQLCompiler.RQLFilterToSQL(const aRQLFIlter: TRQLFilter): string; +var + lValue, lDBFieldName: string; +begin + if aRQLFIlter.RightIsString then + lValue := aRQLFIlter.OpRight.QuotedString('''') + else + lValue := aRQLFIlter.OpRight; + + lDBFieldName := GetDatabaseFieldName(aRQLFIlter.OpLeft); + + case aRQLFIlter.Token of + tkEq: + begin + Result := Format('(%s = %s)', [lDBFieldName, lValue]); + end; + tkLt: + begin + Result := Format('(%s < %s)', [lDBFieldName, lValue]); + end; + tkLe: + begin + Result := Format('(%s <= %s)', [lDBFieldName, lValue]); + end; + tkGt: + begin + Result := Format('(%s > %s)', [lDBFieldName, lValue]); + end; + tkGe: + begin + Result := Format('(%s >= %s)', [lDBFieldName, lValue]); + end; + tkNe: + begin + Result := Format('(%s != %s)', [lDBFieldName, lValue]); + end; + end; +end; + +function TRQLPostgreSQLCompiler.RQLLimitToSQL(const aRQLLimit: TRQLLimit): string; +begin + Result := Format(' LIMIT %d OFFSET %d', [aRQLLimit.Count, aRQLLimit.Start]); +end; + +function TRQLPostgreSQLCompiler.RQLLogicOperatorToSQL(const aRQLFIlter: TRQLLogicOperator): string; +var + lJoin: string; + lRQLCustom: TRQLCustom; + lFirst: Boolean; +begin + case aRQLFIlter.Token of + tkAnd: + begin + lJoin := ' and '; + end; + tkOr: + begin + lJoin := ' or '; + end; + else + raise ERQLException.Create('Invalid token in RQLLogicOperator'); + end; + + Result := ''; + lFirst := True; + for lRQLCustom in aRQLFIlter.FilterAST do + begin + if not lFirst then + begin + Result := Result + lJoin; + end; + lFirst := False; + Result := Result + RQLCustom2SQL(lRQLCustom); + end; + Result := '(' + Result + ')'; +end; + +function TRQLPostgreSQLCompiler.RQLSortToSQL(const aRQLSort: TRQLSort): string; +var + I: Integer; +begin + Result := ' ORDER BY'; + for I := 0 to aRQLSort.Fields.Count - 1 do + begin + if I > 0 then + Result := Result + ','; + Result := Result + ' ' + GetDatabaseFieldName(aRQLSort.Fields[I]); + if aRQLSort.Signs[I] = '+' then + Result := Result + ' ASC' + else + Result := Result + ' DESC'; + end; +end; + +function TRQLPostgreSQLCompiler.RQLWhereToSQL(const aRQLWhere: TRQLWhere): string; +begin + Result := ' WHERE '; +end; + +procedure TRQLPostgreSQLCompiler.AST2SQL(const aRQLAST: TRQLAbstractSyntaxTree; + out aSQL: string); +var + lBuff: TStringBuilder; + lItem: TRQLCustom; +begin + inherited; + + { + Here you can rearrange tokens in the list, for example: + For firebird and mysql syntax you have: filters, sort, limit (default) + For MSSQL syntax you need to rearrange in: limit, filters, sort + } + + lBuff := TStringBuilder.Create; + try + for lItem in aRQLAST do + begin + lBuff.Append(RQLCustom2SQL(lItem)); + end; + aSQL := lBuff.ToString; + finally + lBuff.Free; + end; +end; + +initialization + +TRQLCompilerRegistry.Instance.RegisterCompiler('postgresql', TRQLPostgreSQLCompiler); + +finalization + +TRQLCompilerRegistry.Instance.UnRegisterCompiler('postgresql'); + +end. From cd77144a2b0bbf48ff1d7fdca73307c4d28ab897 Mon Sep 17 00:00:00 2001 From: "joao.duarte" Date: Thu, 22 Nov 2018 12:00:43 -0200 Subject: [PATCH 2/3] Testing RQL Parser PostgreSQL --- tools/rql2sql/RQL2SQL.dpr | 3 ++- tools/rql2sql/RQL2SQL.dproj | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/rql2sql/RQL2SQL.dpr b/tools/rql2sql/RQL2SQL.dpr index 7c97f8b9..b3332d86 100644 --- a/tools/rql2sql/RQL2SQL.dpr +++ b/tools/rql2sql/RQL2SQL.dpr @@ -6,7 +6,8 @@ uses MVCFramework.RQL.AST2FirebirdSQL in '..\..\sources\MVCFramework.RQL.AST2FirebirdSQL.pas', MVCFramework.RQL.Parser in '..\..\sources\MVCFramework.RQL.Parser.pas', MVCFramework.RQL.AST2MySQL in '..\..\sources\MVCFramework.RQL.AST2MySQL.pas', - MVCFramework.RQL.AST2InterbaseSQL in '..\..\sources\MVCFramework.RQL.AST2InterbaseSQL.pas'; + MVCFramework.RQL.AST2InterbaseSQL in '..\..\sources\MVCFramework.RQL.AST2InterbaseSQL.pas', + MVCFramework.RQL.AST2PostgreSQL in '..\..\sources\MVCFramework.RQL.AST2PostgreSQL.pas'; {$R *.res} diff --git a/tools/rql2sql/RQL2SQL.dproj b/tools/rql2sql/RQL2SQL.dproj index 288ba897..46d270f1 100644 --- a/tools/rql2sql/RQL2SQL.dproj +++ b/tools/rql2sql/RQL2SQL.dproj @@ -110,6 +110,7 @@ + Cfg_2 Base @@ -572,3 +573,11 @@ + + From d0197df7cfe3f9dc6d3bb2fa8ac4e7628bf4b5be Mon Sep 17 00:00:00 2001 From: "joao.duarte" Date: Tue, 27 Nov 2018 15:52:48 -0200 Subject: [PATCH 3/3] Added RQL Operator Contains --- sources/MVCFramework.RQL.AST2MySQL.pas | 4 ++++ sources/MVCFramework.RQL.AST2PostgreSQL.pas | 4 ++++ sources/MVCFramework.RQL.Parser.pas | 14 ++++++++++---- tools/bin/rqlhistory.txt | 1 + 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/sources/MVCFramework.RQL.AST2MySQL.pas b/sources/MVCFramework.RQL.AST2MySQL.pas index 59ed84fa..c1621335 100644 --- a/sources/MVCFramework.RQL.AST2MySQL.pas +++ b/sources/MVCFramework.RQL.AST2MySQL.pas @@ -113,6 +113,10 @@ begin begin Result := Format('(%s != %s)', [lDBFieldName, lValue]); end; + tkContains: + begin + Result := Format('(LOWER(%s) LIKE ''%%%s%%'')', [lDBFieldName, lValue.DeQuotedString.ToLower ]) + end; end; end; diff --git a/sources/MVCFramework.RQL.AST2PostgreSQL.pas b/sources/MVCFramework.RQL.AST2PostgreSQL.pas index 0d60acb4..5f82a6a0 100644 --- a/sources/MVCFramework.RQL.AST2PostgreSQL.pas +++ b/sources/MVCFramework.RQL.AST2PostgreSQL.pas @@ -113,6 +113,10 @@ begin begin Result := Format('(%s != %s)', [lDBFieldName, lValue]); end; + tkContains: + begin + Result := Format('(LOWER(%s) LIKE ''%%%s%%'')', [lDBFieldName, lValue.DeQuotedString.ToLower ]) + end; end; end; diff --git a/sources/MVCFramework.RQL.Parser.pas b/sources/MVCFramework.RQL.Parser.pas index b4b41697..f1a405f5 100644 --- a/sources/MVCFramework.RQL.Parser.pas +++ b/sources/MVCFramework.RQL.Parser.pas @@ -76,8 +76,8 @@ uses type TRQLToken = (tkEq, tkLt, tkLe, tkGt, tkGe, tkNe, tkAnd, tkOr, tkSort, tkLimit, { RQL } tkAmpersand, tkEOF, tkOpenPar, tkClosedPar, - tkComma, tkSemicolon, tkPlus, tkMinus, tkDblQuote, tkQuote, tkSpace, tkUnknown); - + tkComma, tkSemicolon, tkPlus, tkMinus, tkDblQuote, tkQuote, tkSpace, tkContains, tkUnknown); + TRQLCustom = class; TRQLAbstractSyntaxTree = class(TObjectList) @@ -486,6 +486,12 @@ begin fCurrToken := tkLimit; Exit(fCurrToken); end; + if (lChar = 'c') and (C(1) = 'o') and (C(2) = 'n') and (C(3) = 't') and (C(4) = 'a') and (C(5) = 'i') and (C(6) = 'n') and (C(7) = 's') then + begin + Skip(8); + fCurrToken := tkContains; + Exit(fCurrToken); + end; if (lChar = ' ') then begin fCurrToken := tkSpace; @@ -562,7 +568,7 @@ begin Result := True; lTk := GetToken; case lTk of - tkEq, tkLt, tkLe, tkGt, tkGe, tkNe: + tkEq, tkLt, tkLe, tkGt, tkGe, tkNe, tkContains: begin ParseBinOperator(lTk, fAST); end; @@ -640,7 +646,7 @@ begin EatWhiteSpaces; lToken := GetToken; case lToken of - tkEq, tkLt, tkLe, tkGt, tkGe, tkNe: + tkEq, tkLt, tkLe, tkGt, tkGe, tkNe, tkContains: begin ParseBinOperator(lToken, lLogicOp.FilterAST); end; diff --git a/tools/bin/rqlhistory.txt b/tools/bin/rqlhistory.txt index d8250d97..dd9b2637 100644 --- a/tools/bin/rqlhistory.txt +++ b/tools/bin/rqlhistory.txt @@ -1,3 +1,4 @@ +contains(nome,"Joćo") sort(+last_name);limit(0,1) limit(1,5) sort(+nome, -cognome)