refactor: outsource queries from GetRowCount into TSqlProvider

This required to introduce a third version of TSqlProvider.GetSql which works with named parameters packed into a TStringMap
This commit is contained in:
Ansgar Becker
2026-02-16 20:45:23 +01:00
parent 277c0a969a
commit 14f5468af2
5 changed files with 72 additions and 60 deletions

View File

@@ -166,6 +166,7 @@ type
FCreateCodeLoaded: Boolean;
FWasSelected: Boolean;
FConnection: TDBConnection;
FMap: TStringMap;
function GetObjType: String;
function GetImageIndex: Integer;
function GetOverlayImageIndex: Integer;
@@ -185,6 +186,7 @@ type
NodeType, GroupType: TListNodeType;
constructor Create(OwnerConnection: TDBConnection);
destructor Destroy;
procedure Assign(Source: TPersistent); override;
procedure UnloadDetails;
procedure Drop;
@@ -197,6 +199,7 @@ type
function RowCount(Reload: Boolean; ForceExact: Boolean=False): Int64;
function GetCreateCode: String; overload;
function GetCreateCode(RemoveAutoInc, RemoveDefiner: Boolean): String; overload;
function AsStringMap: TStringMap;
property ObjType: String read GetObjType;
property ImageIndex: Integer read GetImageIndex;
property OverlayImageIndex: Integer read GetOverlayImageIndex;
@@ -417,9 +420,9 @@ type
TDBLogEvent = procedure(Msg: String; Category: TDBLogCategory=lcInfo; Connection: TDBConnection=nil) of object;
TDBEvent = procedure(Connection: TDBConnection; Database: String) of object;
TDBDataTypeArray = Array of TDBDataType;
TFeatureOrRequirement = (frSrid, frTimezoneVar, frTemporalTypesFraction, frKillQuery,
frLockedTables, frShowCreateTrigger, frShowWarnings,
frShowCharset, frIntegerDisplayWidth, frShowFunctionStatus, frShowProcedureStatus,
TFeatureOrRequirement = (frSrid, frTimezoneVar, frTemporalTypesFraction,
frShowCreateTrigger, frShowWarnings,
frIntegerDisplayWidth, frShowFunctionStatus, frShowProcedureStatus,
frShowTriggers, frShowEvents, frColumnDefaultParentheses,
frHelpKeyword, frEditVariables, frCreateView, frCreateProcedure, frCreateFunction,
frCreateTrigger, frCreateEvent, frInvisibleColumns, frCompressedColumns);
@@ -644,7 +647,6 @@ type
function GetAllDatabases: TStringList; override;
function GetTableEngines: TStringList; override;
function GetCreateViewCode(Database, Name: String): String;
function GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64; override;
procedure FetchDbObjects(db: String; var Cache: TDBObjectList); override;
public
constructor Create(AOwner: TComponent); override;
@@ -674,7 +676,6 @@ type
function GetLastErrorCode: Cardinal; override;
function GetLastErrorMsg: String; override;
function GetAllDatabases: TStringList; override;
function GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64; override;
procedure FetchDbObjects(db: String; var Cache: TDBObjectList); override;
public
constructor Create(AOwner: TComponent); override;
@@ -714,7 +715,6 @@ type
function Ping(Reconnect: Boolean): Boolean; override;
function GetCreateCode(Obj: TDBObject): String; override;
function ConnectionInfo: TStringList; override;
function GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64; override;
property LastRawResults: TPGRawResults read FLastRawResults;
property RegClasses: TOidStringPairs read FRegClasses;
function GetTableKeys(Table: TDBObject): TTableKeyList; override;
@@ -6621,11 +6621,8 @@ begin
frTimezoneVar: Result := ServerVersionInt >= 40103;
frTemporalTypesFraction: Result := (FParameters.IsMariaDB and (ServerVersionInt >= 50300)) or
(FParameters.IsMySQL(True) and (ServerVersionInt >= 50604));
frKillQuery: Result := (not FParameters.IsMySQLonRDS) and (ServerVersionInt >= 50000);
frLockedTables: Result := (not FParameters.IsProxySQLAdmin) and (ServerVersionInt >= 50124);
frShowCreateTrigger: Result := ServerVersionInt >= 50121;
frShowWarnings: Result := ServerVersionInt >= 40100;
frShowCharset: Result := ServerVersionInt >= 40100;
frIntegerDisplayWidth: Result := (FParameters.IsMySQL(True) and (ServerVersionInt < 80017)) or
(not FParameters.IsMySQL(True));
frShowFunctionStatus: Result := (not Parameters.IsProxySQLAdmin) and (ServerVersionInt >= 50000);
@@ -6651,59 +6648,20 @@ end;
function TDBConnection.GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64;
var
Rows: String;
Rows, QueryApprox, QueryExact: String;
RowsColumn: Integer;
begin
// Get row number from a table
Rows := GetVar('SELECT COUNT(*) FROM '+QuotedDbAndTableName(Obj.Database, Obj.Name), 0);
Result := MakeInt(Rows);
end;
function TMySQLConnection.GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64;
var
Rows: String;
begin
// Get row number from a mysql table
if Parameters.IsProxySQLAdmin or ForceExact then begin
Result := inherited
QueryApprox := FSqlProvider.GetSql(qGetRowCountApprox, Obj.AsStringMap);
if QueryApprox.IsEmpty or ForceExact then begin
QueryExact := FSqlProvider.GetSql(qGetRowCountExact, Obj.AsStringMap);
Rows := GetVar(QueryExact);
end
else begin
Rows := GetVar('SHOW TABLE STATUS LIKE '+EscapeString(Obj.Name), 'Rows');
Result := MakeInt(Rows);
// This is ugly: in MySQL 4.x we only have SHOW TABLE STATUS, which cannot be limited to the "Rows" column
RowsColumn := IfThen(QueryApprox.StartsWith('SHOW ', True), 4, 0);
Rows := GetVar(QueryApprox, RowsColumn);
end;
end;
function TAdoDBConnection.GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64;
var
Rows: String;
begin
// Get row number from a mssql table
if (ServerVersionInt < 900) or ForceExact then begin
Result := inherited
end
else begin
Rows := GetVar('SELECT SUM('+QuoteIdent('rows')+') FROM '+QuoteIdent('sys')+'.'+QuoteIdent('partitions')+
' WHERE '+QuoteIdent('index_id')+' IN (0, 1)'+
' AND '+QuoteIdent('object_id')+' = object_id('+EscapeString(Obj.Database+'.'+Obj.Schema+'.'+Obj.Name)+')'
);
Result := MakeInt(Rows);
end;
end;
function TPgConnection.GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64;
var
Rows: String;
begin
// Get row number from a postgres table
Rows := GetVar('SELECT '+QuoteIdent('reltuples')+'::bigint FROM '+QuoteIdent('pg_class')+
' LEFT JOIN '+QuoteIdent('pg_namespace')+
' ON ('+QuoteIdent('pg_namespace')+'.'+QuoteIdent('oid')+' = '+QuoteIdent('pg_class')+'.'+QuoteIdent('relnamespace')+')'+
' WHERE '+QuoteIdent('pg_class')+'.'+QuoteIdent('relkind')+'='+EscapeString('r')+
' AND '+QuoteIdent('pg_namespace')+'.'+QuoteIdent('nspname')+'='+EscapeString(Obj.Database)+
' AND '+QuoteIdent('pg_class')+'.'+QuoteIdent('relname')+'='+EscapeString(Obj.Name)
);
Result := MakeInt(Rows);
end;
@@ -10183,6 +10141,13 @@ begin
FCreateCodeLoaded := False;
FWasSelected := False;
FConnection := OwnerConnection;
FMap := TStringMap.Create;
end;
destructor TDBObject.Destroy;
begin
FMap.Free;
inherited;
end;
@@ -10532,6 +10497,20 @@ begin
Result.Assign(CheckConstraintsInCache);
end;
function TDBObject.AsStringMap: TStringMap;
begin
FMap.Clear;
FMap.Add('EscapedName', FConnection.EscapeString(Name));
FMap.Add('EscapedSchema', FConnection.EscapeString(Schema));
FMap.Add('EscapedDatabase', FConnection.EscapeString(Database));
FMap.Add('EscapedDbSchemaName', FConnection.EscapeString(Database+'.'+Schema+'.'+Name));
FMap.Add('QuotedDatabase', QuotedDatabase);
FMap.Add('QuotedName', QuotedName);
FMap.Add('QuotedDbAndTableName', QuotedDbAndTableName);
Result := FMap;
end;
{ *** TTableColumn }

View File

@@ -476,6 +476,11 @@ begin
''''' AS "Default", '''' AS "Compiled", '+
'1 AS "Sortlen"';
qGetCharsets: Result := 'SELECT name AS Charset, description AS Description FROM master.sys.syscharsets';
qGetRowCountApprox: Result := IfThen(
FServerVersion >= 900,
'SELECT SUM("rows") FROM "sys"."partitions" WHERE "index_id" IN (0, 1) AND "object_id" = object_id(:EscapedDbSchemaName)',
''
);
else Result := inherited;
end;
end;

View File

@@ -3316,6 +3316,11 @@ begin
'SHOW CHARSET',
''
);
qGetRowCountApprox: Result := IfThen(
FNetType <> ntMySQL_ProxySQLAdmin,
'SHOW TABLE STATUS LIKE :EscapedName',
''
);
else Result := inherited;
end;
end;

View File

@@ -36,6 +36,7 @@ type
TNetTypeLibs = TDictionary<TNetType, TStringList>;
// SQL query ids and provider
TStringMap = TDictionary<string,string>;
TQueryId = (qDatabaseTable, qDatabaseTableId, qDatabaseDrop,
qDbObjectsTable, qDbObjectsCreateCol, qDbObjectsUpdateCol, qDbObjectsTypeCol,
qEmptyTable, qRenameTable, qRenameView, qCurrentUserHost, qLikeCompare,
@@ -45,7 +46,7 @@ type
qUSEQuery, qKillQuery, qKillProcess,
qFuncLength, qFuncCeil, qFuncLeft, qFuncNow, qFuncLastAutoIncNumber,
qLockedTables, qDisableForeignKeyChecks, qEnableForeignKeyChecks,
qOrderAsc, qOrderDesc,
qOrderAsc, qOrderDesc, qGetRowCountExact, qGetRowCountApprox,
qForeignKeyDrop, qGetTableColumns, qGetCollations, qGetCollationsExtended, qGetCharsets);
TSqlProvider = class
strict protected
@@ -54,8 +55,12 @@ type
public
constructor Create(ANetType: TNetType);
function Has(AId: TQueryId): Boolean;
// Base version, just returns the original SQL string
function GetSql(AId: TQueryId): string; overload; virtual;
// Version for simple strings passed to Format()
function GetSql(AId: TQueryId; const Args: array of const): string; overload;
// Version for named parameters
function GetSql(AId: TQueryId; NamedParameters: TStringMap): string; overload;
property ServerVersion: Integer read FServerVersion write FServerVersion;
end;
@@ -203,6 +208,7 @@ begin
qForeignKeyEventAction: Result := 'RESTRICT,CASCADE,SET NULL,NO ACTION';
qOrderAsc: Result := 'ASC';
qOrderDesc: Result := 'DESC';
qGetRowCountExact: Result := 'SELECT COUNT(*) FROM :QuotedDbAndTableName';
else Result := '';
end;
end;
@@ -210,10 +216,22 @@ end;
function TSqlProvider.GetSql(AId: TQueryId; const Args: array of const): string;
begin
Result := GetSql(AId);
if not Result.IsEmpty then
Result := Format(Result, Args);
if Result.IsEmpty then
Exit;
Result := Format(Result, Args);
end;
function TSqlProvider.GetSql(AId: TQueryId; NamedParameters: TStringMap): string;
var
Key: String;
begin
Result := GetSql(AId);
if Result.IsEmpty then
Exit;
for Key in NamedParameters.Keys do begin
Result := StringReplace(Result, ':'+Key, NamedParameters[Key], [rfReplaceAll]);
end;
end;

View File

@@ -709,6 +709,11 @@ begin
'(SELECT conforencoding AS enc FROM pg_catalog.pg_conversion '+
' UNION '+
' SELECT contoencoding AS enc FROM pg_catalog.pg_conversion) AS x';
qGetRowCountApprox: Result := 'SELECT reltuples::bigint FROM pg_class'+
' LEFT JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace'+
' WHERE pg_class.relkind=''r'''+
' AND pg_namespace.nspname=:EscapedDatabase'+
' AND pg_class.relname=:EscapedName';
else Result := inherited;
end;
end;