2018-09-28 13:01:46 +02:00
|
|
|
// *************************************************************************** }
|
|
|
|
//
|
|
|
|
// Delphi MVC Framework
|
|
|
|
//
|
2022-01-04 15:44:47 +01:00
|
|
|
// Copyright (c) 2010-2022 Daniele Teti and the DMVCFramework Team
|
2018-09-28 13:01:46 +02:00
|
|
|
//
|
|
|
|
// 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.AST2FirebirdSQL;
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
MVCFramework.RQL.Parser;
|
|
|
|
|
2021-07-27 15:02:49 +02:00
|
|
|
const
|
|
|
|
RQLFirebirdReservedWords: TArray<string> = [
|
|
|
|
'ADD', 'ADMIN', 'ALL', 'ALTER', 'AND', 'ANY', 'AS', 'AT', 'AVG',
|
|
|
|
'BEGIN', 'BETWEEN', 'BIGINT', 'BIT_LENGTH', 'BLOB', 'BOTH', 'BY',
|
|
|
|
'CASE', 'CAST', 'CHAR', 'CHAR_LENGTH', 'CHARACTER', 'CHARACTER_LENGTH',
|
|
|
|
'CHECK', 'CLOSE', 'COLLATE', 'COLUMN', 'COMMIT', 'CONNECT', 'CONSTRAINT',
|
|
|
|
'COUNT', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_CONNECTION',
|
|
|
|
'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
|
|
|
|
'CURRENT_TRANSACTION', 'CURRENT_USER', 'CURSOR',
|
|
|
|
'DATE', 'DAY', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELETE',
|
|
|
|
'DISCONNECT', 'DISTINCT', 'DOUBLE', 'DROP',
|
|
|
|
'ELSE', 'END', 'ESCAPE', 'EXECUTE', 'EXISTS', 'EXTERNAL', 'EXTRACT',
|
|
|
|
'FETCH', 'FILTER', 'FLOAT', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'FUNCTION',
|
|
|
|
'GDSCODE', 'GLOBAL', 'GRANT', 'GROUP',
|
|
|
|
'HAVING', 'HOUR',
|
|
|
|
'IN', 'INDEX', 'INNER', 'INSENSITIVE', 'INSERT', 'INT', 'INTEGER', 'INTO', 'IS',
|
|
|
|
'JOIN',
|
|
|
|
'LEADING', 'LEFT', 'LIKE', 'LONG', 'LOWER',
|
|
|
|
'MAX', 'MAXIMUM_SEGMENT', 'MERGE', 'MIN', 'MINUTE', 'MONTH',
|
|
|
|
'NATIONAL', 'NATURAL', 'NCHAR', 'NO', 'NOT', 'NULL', 'NUMERIC',
|
|
|
|
'OCTET_LENGTH', 'OF', 'ON', 'ONLY', 'OPEN', 'OR', 'ORDER', 'OUTER',
|
|
|
|
'PARAMETER', 'PLAN', 'POSITION', 'POST_EVENT', 'PRECISION', 'PRIMARY', 'PROCEDURE',
|
|
|
|
'RDB$DB_KEY', 'REAL', 'RECORD_VERSION', 'RECREATE', 'RECURSIVE', 'REFERENCES', 'RELEASE',
|
|
|
|
'RETURNING_VALUES', 'RETURNS', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROW_COUNT', 'ROWS',
|
|
|
|
'SAVEPOINT', 'SECOND', 'SELECT', 'SENSITIVE', 'SET', 'SIMILAR', 'SMALLINT', 'SOME',
|
|
|
|
'SQLCODE', 'SQLSTATE', 'START', 'SUM',
|
|
|
|
'TABLE', 'THEN', 'TIME', 'TIMESTAMP', 'TO', 'TRAILING', 'TRIGGER', 'TRIM',
|
|
|
|
'UNION', 'UNIQUE', 'UPDATE', 'UPPER', 'USER', 'USING',
|
|
|
|
'VALUE', 'VALUES', 'VARCHAR', 'VARIABLE', 'VARYING', 'VIEW',
|
|
|
|
'WHEN', 'WHERE', 'WHILE', 'WITH',
|
|
|
|
'YEAR'];
|
|
|
|
|
2018-09-28 13:01:46 +02:00
|
|
|
type
|
|
|
|
TRQLFirebirdCompiler = class(TRQLCompiler)
|
2018-11-02 21:43:09 +01:00
|
|
|
protected
|
2022-04-12 12:12:08 +02:00
|
|
|
function GetLiteralBoolean(const Value: Boolean): String; virtual;
|
2018-11-02 21:43:09 +01:00
|
|
|
function RQLFilterToSQL(const aRQLFIlter: TRQLFilter): string; virtual;
|
|
|
|
function RQLSortToSQL(const aRQLSort: TRQLSort): string; virtual;
|
|
|
|
function RQLLimitToSQL(const aRQLLimit: TRQLLimit): string; virtual;
|
|
|
|
function RQLWhereToSQL(const aRQLWhere: TRQLWhere): string; virtual;
|
|
|
|
function RQLLogicOperatorToSQL(const aRQLFIlter: TRQLLogicOperator): string; virtual;
|
|
|
|
function RQLCustom2SQL(const aRQLCustom: TRQLCustom): string; virtual;
|
2018-09-28 13:01:46 +02:00
|
|
|
public
|
2018-10-23 16:18:34 +02:00
|
|
|
procedure AST2SQL(const aRQLAST: TRQLAbstractSyntaxTree; out aSQL: string); override;
|
2021-07-27 15:02:49 +02:00
|
|
|
function GetFieldNameForSQL(const FieldName: string): string; override;
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
2018-11-02 21:43:09 +01:00
|
|
|
System.SysUtils,
|
2021-07-27 15:02:49 +02:00
|
|
|
System.StrUtils,
|
2018-11-02 21:43:09 +01:00
|
|
|
MVCFramework.ActiveRecord;
|
2018-09-28 13:01:46 +02:00
|
|
|
|
|
|
|
{ TRQLFirebirdCompiler }
|
|
|
|
|
2021-07-27 15:02:49 +02:00
|
|
|
function TRQLFirebirdCompiler.GetFieldNameForSQL(const FieldName: string): string;
|
|
|
|
begin
|
|
|
|
if MatchStr(FieldName.ToUpper, RQLFirebirdReservedWords) then
|
|
|
|
Result := FieldName.QuotedString('"')
|
|
|
|
else
|
|
|
|
Result := inherited;
|
|
|
|
end;
|
|
|
|
|
2022-04-12 12:12:08 +02:00
|
|
|
function TRQLFirebirdCompiler.GetLiteralBoolean(const Value: Boolean): String;
|
|
|
|
begin
|
|
|
|
if Value then
|
|
|
|
begin
|
|
|
|
Exit('true');
|
|
|
|
end;
|
|
|
|
Exit('false');
|
|
|
|
end;
|
|
|
|
|
2018-09-28 13:01:46 +02:00
|
|
|
function TRQLFirebirdCompiler.RQLCustom2SQL(
|
|
|
|
const aRQLCustom: TRQLCustom): string;
|
|
|
|
begin
|
|
|
|
if aRQLCustom is TRQLFilter then
|
|
|
|
begin
|
|
|
|
Result := RQLFilterToSQL(TRQLFilter(aRQLCustom));
|
|
|
|
end
|
2019-05-19 11:06:03 +02:00
|
|
|
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
|
2018-09-28 13:01:46 +02:00
|
|
|
else
|
2019-05-19 11:06:03 +02:00
|
|
|
raise ERQLException.CreateFmt('Unknown token in compiler: %s', [aRQLCustom.ClassName]);
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
function TRQLFirebirdCompiler.RQLFilterToSQL(const aRQLFIlter: TRQLFilter): string;
|
|
|
|
var
|
|
|
|
lValue, lDBFieldName: string;
|
|
|
|
begin
|
2019-05-19 11:06:03 +02:00
|
|
|
if aRQLFIlter.RightValueType = vtString then
|
2018-09-28 13:01:46 +02:00
|
|
|
lValue := aRQLFIlter.OpRight.QuotedString('''')
|
2019-06-11 21:46:24 +02:00
|
|
|
else if aRQLFIlter.RightValueType = vtBoolean then
|
|
|
|
begin
|
|
|
|
if SameText(aRQLFIlter.OpRight, 'true') then
|
2022-04-12 12:12:08 +02:00
|
|
|
lValue := GetLiteralBoolean(true)
|
2019-06-11 21:46:24 +02:00
|
|
|
else
|
2022-04-12 12:12:08 +02:00
|
|
|
lValue := GetLiteralBoolean(false);
|
2019-06-11 21:46:24 +02:00
|
|
|
end
|
2018-09-28 13:01:46 +02:00
|
|
|
else
|
|
|
|
lValue := aRQLFIlter.OpRight;
|
|
|
|
|
2021-11-21 19:27:06 +01:00
|
|
|
lDBFieldName := GetDatabaseFieldName(aRQLFIlter.OpLeft, True);
|
2018-09-28 13:01:46 +02:00
|
|
|
|
|
|
|
case aRQLFIlter.Token of
|
|
|
|
tkEq:
|
|
|
|
begin
|
2019-07-02 16:42:42 +02:00
|
|
|
if aRQLFIlter.RightValueType = vtNull then
|
|
|
|
Result := Format('(%s IS NULL)', [lDBFieldName])
|
|
|
|
else
|
|
|
|
Result := Format('(%s = %s)', [lDBFieldName, lValue]);
|
2018-09-28 13:01:46 +02:00
|
|
|
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
|
2019-07-02 16:42:42 +02:00
|
|
|
if aRQLFIlter.RightValueType = vtNull then
|
|
|
|
Result := Format('(%s IS NOT NULL)', [lDBFieldName])
|
|
|
|
else
|
|
|
|
Result := Format('(%s != %s)', [lDBFieldName, lValue]);
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
2019-01-18 18:11:27 +01:00
|
|
|
tkContains:
|
|
|
|
begin
|
2020-02-14 00:15:25 +01:00
|
|
|
Result := Format('(%s containing %s)', [lDBFieldName, lValue.ToLower])
|
2019-01-18 18:11:27 +01:00
|
|
|
end;
|
2019-03-25 15:04:59 +01:00
|
|
|
tkIn:
|
|
|
|
begin
|
2019-05-19 11:06:03 +02:00
|
|
|
case aRQLFIlter.RightValueType of
|
|
|
|
vtIntegerArray: // if array is empty, RightValueType is always vtIntegerArray
|
|
|
|
begin
|
|
|
|
Result := Format('(%s IN (%s))', [
|
|
|
|
lDBFieldName, string.Join(',', aRQLFIlter.OpRightArray)
|
|
|
|
]);
|
|
|
|
end;
|
|
|
|
vtStringArray:
|
|
|
|
begin
|
2020-02-14 00:15:25 +01:00
|
|
|
Result := Format('(%s IN (%s))', [
|
|
|
|
lDBFieldName, string.Join(',', QuoteStringArray(aRQLFIlter.OpRightArray))
|
2019-05-19 11:06:03 +02:00
|
|
|
]);
|
|
|
|
end;
|
|
|
|
else
|
|
|
|
raise ERQLException.Create('Invalid RightValueType for tkIn');
|
|
|
|
end;
|
2019-03-25 15:04:59 +01:00
|
|
|
end;
|
2020-05-13 20:29:04 +02:00
|
|
|
tkOut:
|
|
|
|
begin
|
|
|
|
case aRQLFIlter.RightValueType of
|
|
|
|
vtIntegerArray:
|
|
|
|
begin
|
|
|
|
Result := Format('(%s NOT IN (%s))', [
|
|
|
|
lDBFieldName, string.Join(',', aRQLFIlter.OpRightArray)
|
|
|
|
]);
|
|
|
|
end;
|
|
|
|
vtStringArray:
|
|
|
|
begin
|
|
|
|
Result := Format('(%s NOT IN (%s))', [
|
|
|
|
lDBFieldName, string.Join(',', QuoteStringArray(aRQLFIlter.OpRightArray))
|
|
|
|
]);
|
|
|
|
end;
|
|
|
|
else
|
|
|
|
raise ERQLException.Create('Invalid RightValueType for tkOut');
|
|
|
|
end;
|
|
|
|
end;
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TRQLFirebirdCompiler.RQLLimitToSQL(const aRQLLimit: TRQLLimit): string;
|
|
|
|
begin
|
|
|
|
// firebird ROWS requires Start > 0. Limit function is 0 based, so we have to add 1 to start.
|
2018-12-17 00:39:29 +01:00
|
|
|
Result := Format(' /*limit*/ ROWS %d to %d', [aRQLLimit.Start + 1, aRQLLimit.Start + aRQLLimit.Count]);
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
function TRQLFirebirdCompiler.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 TRQLFirebirdCompiler.RQLSortToSQL(const aRQLSort: TRQLSort): string;
|
|
|
|
var
|
|
|
|
I: Integer;
|
|
|
|
begin
|
2018-12-17 00:39:29 +01:00
|
|
|
Result := ' /*sort*/ ORDER BY';
|
2018-09-28 13:01:46 +02:00
|
|
|
for I := 0 to aRQLSort.Fields.Count - 1 do
|
|
|
|
begin
|
|
|
|
if I > 0 then
|
|
|
|
Result := Result + ',';
|
2021-11-21 19:27:06 +01:00
|
|
|
Result := Result + ' ' + GetDatabaseFieldName(aRQLSort.Fields[I], True);
|
2018-09-28 13:01:46 +02:00
|
|
|
if aRQLSort.Signs[I] = '+' then
|
|
|
|
Result := Result + ' ASC'
|
|
|
|
else
|
|
|
|
Result := Result + ' DESC';
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TRQLFirebirdCompiler.RQLWhereToSQL(const aRQLWhere: TRQLWhere): string;
|
|
|
|
begin
|
|
|
|
Result := ' where ';
|
|
|
|
end;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
procedure TRQLFirebirdCompiler.AST2SQL(const aRQLAST: TRQLAbstractSyntaxTree;
|
2018-09-28 13:01:46 +02:00
|
|
|
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
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
TRQLCompilerRegistry.Instance.RegisterCompiler('firebird', TRQLFirebirdCompiler);
|
2018-09-28 13:01:46 +02:00
|
|
|
|
|
|
|
finalization
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
TRQLCompilerRegistry.Instance.UnRegisterCompiler('firebird');
|
2018-09-28 13:01:46 +02:00
|
|
|
|
|
|
|
end.
|