mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-06 18:24:26 +08:00
961 lines
30 KiB
ObjectPascal
961 lines
30 KiB
ObjectPascal
unit mysql_connection;
|
|
|
|
{$M+} // Needed to add published properties
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, windows, mysql_api, mysql_structures, WideStrings, WideStrUtils, cUnicodeCodecs, SynRegExpr;
|
|
|
|
type
|
|
|
|
{ TMySQLConnection }
|
|
|
|
TMySQLLogCategory = (lcInfo, lcSQL, lcError, lcWarning, lcDebug);
|
|
TMySQLLogEvent = procedure(Msg: WideString; Category: TMySQLLogCategory=lcInfo) of object;
|
|
TMySQLDatabaseChangedEvent = procedure(Database: WideString) of object;
|
|
|
|
TMySQLServerCapability = (
|
|
cpShowEngines, // SHOW ENGINES
|
|
cpShowTableStatus, // SHOW TABLE STATUS
|
|
cpShowFullTables, // SHOW FULL TABLES
|
|
cpShowCreateTable, // SHOW CREATE TABLE foo
|
|
cpShowCreateDatabase, // SHOW CREATE DATABASE foo
|
|
cpHelpSystem, // HELP "foo"
|
|
cpSetNames, // SET NAMES
|
|
cpCalcFoundRows, // SELECT SQL_CALC_FOUND_ROWS ...
|
|
cpLoadFile, // LOAD DATA LOCAL INFILE ...
|
|
cpTableComment, // CREATE TABLE ... COMMENT = "foo"
|
|
cpFieldComment, // ALTER TABLE ADD ... COMMENT = "foo"
|
|
cpColumnMoving, // ALTER TABLE CHANGE ... FIRST|AFTER foo
|
|
cpTruncateTable, // TRUNCATE TABLE foo
|
|
cpAlterDatabase, // ALTER DATABASE
|
|
cpRenameDatabase // RENAME DATABASE
|
|
);
|
|
TMySQLServerCapabilities = set of TMySQLServerCapability;
|
|
|
|
TMySQLClientOption = (
|
|
opCompress, // CLIENT_COMPRESS
|
|
opConnectWithDb, // CLIENT_CONNECT_WITH_DB
|
|
opFoundRows, // CLIENT_FOUND_ROWS
|
|
opIgnoreSigpipe, // CLIENT_IGNORE_SIGPIPE
|
|
opIgnoreSpace, // CLIENT_IGNORE_SPACE
|
|
opInteractive, // CLIENT_INTERACTIVE
|
|
opLocalFiles, // CLIENT_LOCAL_FILES
|
|
opLongFlag, // CLIENT_LONG_FLAG
|
|
opLongPassword, // CLIENT_LONG_PASSWORD
|
|
opMultiResults, // CLIENT_MULTI_RESULTS
|
|
opMultiStatements, // CLIENT_MULTI_STATEMENTS
|
|
opNoSchema, // CLIENT_NO_SCHEMA
|
|
opODBC, // CLIENT_ODBC
|
|
opProtocol41, // CLIENT_PROTOCOL_41
|
|
opRememberOptions, // CLIENT_REMEMBER_OPTIONS
|
|
opReserved, // CLIENT_RESERVED
|
|
opSecureConnection, // CLIENT_SECURE_CONNECTION
|
|
opSSL, // CLIENT_SSL
|
|
opTransactions // CLIENT_TRANSACTIONS
|
|
);
|
|
TMySQLClientOptions = set of TMySQLClientOption;
|
|
|
|
const
|
|
DEFAULT_MYSQLOPTIONS = [opCompress, opLocalFiles, opInteractive, opProtocol41, opMultiStatements];
|
|
|
|
type
|
|
TMySQLQuery = class;
|
|
TMySQLConnection = class(TComponent)
|
|
private
|
|
FHandle: PMYSQL;
|
|
FActive: Boolean;
|
|
FConnectionStarted: Integer;
|
|
FServerStarted: Integer;
|
|
FHostname: String;
|
|
FSocketname: String;
|
|
FPort: Integer;
|
|
FUsername: String;
|
|
FPassword: String;
|
|
FDatabase: WideString;
|
|
FLogPrefix: WideString;
|
|
FOnLog: TMySQLLogEvent;
|
|
FOnDatabaseChanged: TMySQLDatabaseChangedEvent;
|
|
FOptions: TMySQLClientOptions;
|
|
FCapabilities: TMySQLServerCapabilities;
|
|
FRowsFound: Int64;
|
|
FRowsAffected: Int64;
|
|
FServerVersionUntouched: String;
|
|
FLastQueryDuration, FLastQueryNetworkDuration: Cardinal;
|
|
FIsUnicode: Boolean;
|
|
FTableEngines: TStringList;
|
|
FTableEngineDefault: String;
|
|
FCollationTable: TMySQLQuery;
|
|
FCharsetTable: TMySQLQuery;
|
|
FInformationSchemaObjects: TWideStringlist;
|
|
procedure SetActive(Value: Boolean);
|
|
procedure SetDatabase(Value: WideString);
|
|
function GetThreadId: Cardinal;
|
|
function GetCharacterSet: String;
|
|
procedure SetCharacterSet(CharsetName: String);
|
|
function GetLastError: WideString;
|
|
function GetServerVersionStr: String;
|
|
function GetServerVersionInt: Integer;
|
|
function GetTableEngines: TStringList;
|
|
function GetCollationTable: TMySQLQuery;
|
|
function GetCollationList: TStringList;
|
|
function GetCharsetTable: TMySQLQuery;
|
|
function GetCharsetList: TStringList;
|
|
function GetInformationSchemaObjects: TWideStringlist;
|
|
function GetConnectionUptime: Integer;
|
|
function GetServerUptime: Integer;
|
|
procedure Log(Category: TMySQLLogCategory; Msg: WideString);
|
|
procedure DetectCapabilities;
|
|
procedure ClearCache;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function Query(SQL: WideString; DoStoreResult: Boolean=False): PMYSQL_RES;
|
|
function EscapeString(Text: WideString; DoQuote: Boolean=True): WideString;
|
|
function QuoteIdent(Identifier: WideString): WideString;
|
|
function DeQuoteIdent(Identifier: WideString): WideString;
|
|
function ConvertServerVersion(Version: Integer): String;
|
|
function GetResults(SQL: WideString): TMySQLQuery;
|
|
function GetCol(SQL: WideString; Column: Integer=0): TWideStringList;
|
|
function GetVar(SQL: WideString; Column: Integer=0): WideString; overload;
|
|
function GetVar(SQL: WideString; Column: WideString): WideString; overload;
|
|
function Ping: Boolean;
|
|
property ThreadId: Cardinal read GetThreadId;
|
|
property ConnectionUptime: Integer read GetConnectionUptime;
|
|
property ServerUptime: Integer read GetServerUptime;
|
|
property CharacterSet: String read GetCharacterSet write SetCharacterSet;
|
|
property LastError: WideString read GetLastError;
|
|
property ServerVersionUntouched: String read FServerVersionUntouched;
|
|
property ServerVersionStr: String read GetServerVersionStr;
|
|
property ServerVersionInt: Integer read GetServerVersionInt;
|
|
property Capabilities: TMySQLServerCapabilities read FCapabilities;
|
|
property RowsFound: Int64 read FRowsFound;
|
|
property RowsAffected: Int64 read FRowsAffected;
|
|
property LastQueryDuration: Cardinal read FLastQueryDuration;
|
|
property LastQueryNetworkDuration: Cardinal read FLastQueryNetworkDuration;
|
|
property IsUnicode: Boolean read FIsUnicode;
|
|
property TableEngines: TStringList read GetTableEngines;
|
|
property TableEngineDefault: String read FTableEngineDefault;
|
|
property CollationTable: TMySQLQuery read GetCollationTable;
|
|
property CollationList: TStringList read GetCollationList;
|
|
property CharsetTable: TMySQLQuery read GetCharsetTable;
|
|
property CharsetList: TStringList read GetCharsetList;
|
|
property InformationSchemaObjects: TWideStringlist read GetInformationSchemaObjects;
|
|
published
|
|
property Active: Boolean read FActive write SetActive default False;
|
|
property Hostname: String read FHostname write FHostname;
|
|
property Socketname: String read FSocketname write FSocketname;
|
|
property Port: Integer read FPort write FPort default MYSQL_PORT;
|
|
property Username: String read FUsername write FUsername;
|
|
property Password: String read FPassword write FPassword;
|
|
property Database: WideString read FDatabase write SetDatabase;
|
|
property Options: TMySQLClientOptions read FOptions write FOptions default [opCompress, opLocalFiles, opInteractive, opProtocol41];
|
|
property LogPrefix: WideString read FLogPrefix write FLogPrefix;
|
|
// Events
|
|
property OnLog: TMySQLLogEvent read FOnLog write FOnLog;
|
|
property OnDatabaseChanged: TMySQLDatabaseChangedEvent read FOnDatabaseChanged write FOnDatabaseChanged;
|
|
end;
|
|
|
|
|
|
{ TMySQLQuery }
|
|
|
|
TMySQLQuery = class(TComponent)
|
|
private
|
|
FSQL: WideString;
|
|
FConnection: TMySQLConnection;
|
|
FRecNo,
|
|
FRecordCount: Int64;
|
|
FColumnNames: TWideStringList;
|
|
FLastResult: PMYSQL_RES;
|
|
FCurrentRow: PMYSQL_ROW;
|
|
FEof: Boolean;
|
|
FDatatypes: Array of TDatatype;
|
|
procedure SetSQL(Value: WideString);
|
|
procedure SetRecNo(Value: Int64);
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure Execute;
|
|
procedure First;
|
|
procedure Next;
|
|
function ColumnCount: Integer;
|
|
function Col(Column: Integer; IgnoreErrors: Boolean=False): WideString; overload;
|
|
function Col(ColumnName: WideString; IgnoreErrors: Boolean=False): WideString; overload;
|
|
function DataType(Column: Integer): TDataType;
|
|
function ColExists(Column: WideString): Boolean;
|
|
function ColIsPrimaryKeyPart(Column: Integer): Boolean;
|
|
function IsNull(Column: Integer): Boolean;
|
|
function HasResult: Boolean;
|
|
property RecNo: Int64 read FRecNo write SetRecNo;
|
|
property Eof: Boolean read FEof;
|
|
property RecordCount: Int64 read FRecordCount;
|
|
property ColumnNames: TWideStringList read FColumnNames;
|
|
published
|
|
property SQL: WideString read FSQL write SetSQL;
|
|
property Connection: TMySQLConnection read FConnection write FConnection;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
|
|
{ TMySQLConnection }
|
|
|
|
constructor TMySQLConnection.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FOptions := DEFAULT_MYSQLOPTIONS;
|
|
FPort := MYSQL_PORT;
|
|
FRowsFound := 0;
|
|
FRowsAffected := 0;
|
|
FConnectionStarted := 0;
|
|
FLastQueryDuration := 0;
|
|
FLastQueryNetworkDuration := 0;
|
|
FLogPrefix := '';
|
|
FIsUnicode := False;
|
|
end;
|
|
|
|
|
|
destructor TMySQLConnection.Destroy;
|
|
begin
|
|
if Active then Active := False;
|
|
ClearCache;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
{**
|
|
(Dis-)Connect to/from server
|
|
}
|
|
procedure TMySQLConnection.SetActive( Value: Boolean );
|
|
var
|
|
Connected: PMYSQL;
|
|
ClientFlags: Integer;
|
|
Error, tmpdb: WideString;
|
|
UsingPass, Protocol, CurCharset: String;
|
|
begin
|
|
FActive := Value;
|
|
|
|
if Value and (FHandle = nil) then begin
|
|
// Get handle
|
|
FHandle := mysql_init(nil);
|
|
|
|
// Gather client options
|
|
ClientFlags := 0;
|
|
if opRememberOptions in FOptions then ClientFlags := ClientFlags or CLIENT_REMEMBER_OPTIONS;
|
|
if opLongPassword in FOptions then ClientFlags := ClientFlags or CLIENT_LONG_PASSWORD;
|
|
if opFoundRows in FOptions then ClientFlags := ClientFlags or CLIENT_FOUND_ROWS;
|
|
if opLongFlag in FOptions then ClientFlags := ClientFlags or CLIENT_LONG_FLAG;
|
|
if opConnectWithDb in FOptions then ClientFlags := ClientFlags or CLIENT_CONNECT_WITH_DB;
|
|
if opNoSchema in FOptions then ClientFlags := ClientFlags or CLIENT_NO_SCHEMA;
|
|
if opCompress in FOptions then ClientFlags := ClientFlags or CLIENT_COMPRESS;
|
|
if opODBC in FOptions then ClientFlags := ClientFlags or CLIENT_ODBC;
|
|
if opLocalFiles in FOptions then ClientFlags := ClientFlags or CLIENT_LOCAL_FILES;
|
|
if opIgnoreSpace in FOptions then ClientFlags := ClientFlags or CLIENT_IGNORE_SPACE;
|
|
if opProtocol41 in FOptions then ClientFlags := ClientFlags or CLIENT_PROTOCOL_41;
|
|
if opInteractive in FOptions then ClientFlags := ClientFlags or CLIENT_INTERACTIVE;
|
|
if opSSL in FOptions then ClientFlags := ClientFlags or CLIENT_SSL;
|
|
if opIgnoreSigpipe in FOptions then ClientFlags := ClientFlags or CLIENT_IGNORE_SIGPIPE;
|
|
if opTransactions in FOptions then ClientFlags := ClientFlags or CLIENT_TRANSACTIONS;
|
|
if opReserved in FOptions then ClientFlags := ClientFlags or CLIENT_RESERVED;
|
|
if opSecureConnection in FOptions then ClientFlags := ClientFlags or CLIENT_SECURE_CONNECTION;
|
|
if opMultiStatements in FOptions then ClientFlags := ClientFlags or CLIENT_MULTI_STATEMENTS;
|
|
if opMultiResults in FOptions then ClientFlags := ClientFlags or CLIENT_MULTI_RESULTS;
|
|
if opRememberOptions in FOptions then ClientFlags := ClientFlags or CLIENT_REMEMBER_OPTIONS;
|
|
|
|
// Prepare connection
|
|
if FHostname = '.' then Protocol := 'named pipe' else Protocol := 'TCP/IP';
|
|
if Password <> '' then UsingPass := 'Yes' else UsingPass := 'No';
|
|
Log(lcInfo, 'Connecting to '+Hostname+' via '+Protocol+
|
|
', username '+Username+
|
|
', using password: '+UsingPass+' ...');
|
|
Connected := mysql_real_connect(
|
|
FHandle,
|
|
PChar(FHostname),
|
|
PChar(FUsername),
|
|
PChar(FPassword),
|
|
nil,
|
|
FPort,
|
|
PChar(FSocketname),
|
|
ClientFlags
|
|
);
|
|
if Connected = nil then begin
|
|
Error := LastError;
|
|
Log(lcError, Error);
|
|
FActive := False;
|
|
FConnectionStarted := 0;
|
|
FHandle := nil;
|
|
raise Exception.Create(Error);
|
|
end else begin
|
|
Log(lcInfo, 'Connected. Thread-ID: '+IntToStr(ThreadId));
|
|
CharacterSet := 'utf8';
|
|
CurCharset := CharacterSet;
|
|
Log(lcInfo, 'Characterset: '+CurCharset);
|
|
FIsUnicode := CurCharset = 'utf8';
|
|
FConnectionStarted := GetTickCount div 1000;
|
|
FServerStarted := FConnectionStarted - StrToIntDef(GetVar('SHOW STATUS LIKE ''Uptime''', 1), 1);
|
|
FServerVersionUntouched := mysql_get_server_info(FHandle);
|
|
DetectCapabilities;
|
|
tmpdb := FDatabase;
|
|
FDatabase := '';
|
|
SetDatabase(tmpdb);
|
|
end;
|
|
end
|
|
|
|
else if (not Value) and (FHandle <> nil) then begin
|
|
mysql_close(FHandle);
|
|
FConnectionStarted := 0;
|
|
FHandle := nil;
|
|
FCapabilities := [];
|
|
Log(lcInfo, 'Connection to '+FHostname+' closed at '+DateTimeToStr(Now));
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.Ping: Boolean;
|
|
begin
|
|
if FActive and (mysql_ping(FHandle) <> 0) then begin
|
|
Active := False;
|
|
ClearCache;
|
|
end;
|
|
Result := FActive;
|
|
end;
|
|
|
|
|
|
{**
|
|
Executes a query
|
|
}
|
|
function TMySQLConnection.Query(SQL: WideString; DoStoreResult: Boolean=False): PMYSQL_RES;
|
|
var
|
|
querystatus: Integer;
|
|
NativeSQL: String;
|
|
TimerStart: Cardinal;
|
|
begin
|
|
if not Ping then
|
|
Active := True;
|
|
Log(lcSQL, SQL);
|
|
NativeSQL := UTF8Encode(SQL);
|
|
TimerStart := GetTickCount;
|
|
querystatus := mysql_real_query(FHandle, PChar(NativeSQL), Length(NativeSQL));
|
|
FLastQueryDuration := GetTickCount - TimerStart;
|
|
FLastQueryNetworkDuration := 0;
|
|
if querystatus <> 0 then begin
|
|
Log(lcError, GetLastError);
|
|
raise Exception.Create(GetLastError);
|
|
end else begin
|
|
// We must call mysql_store_result() + mysql_free_result() to unblock the connection
|
|
// See: http://dev.mysql.com/doc/refman/5.0/en/mysql-store-result.html
|
|
FRowsAffected := mysql_affected_rows(FHandle);
|
|
TimerStart := GetTickCount;
|
|
Result := mysql_store_result(FHandle);
|
|
FLastQueryNetworkDuration := GetTickCount - TimerStart;
|
|
if Result <> nil then begin
|
|
FRowsFound := mysql_num_rows(Result);
|
|
FRowsAffected := 0;
|
|
Log(lcDebug, IntToStr(RowsFound)+' rows found.');
|
|
if not DoStoreResult then
|
|
mysql_free_result(Result);
|
|
end else begin
|
|
// Query did not return a result
|
|
FRowsFound := 0;
|
|
Log(lcDebug, IntToStr(RowsAffected)+' rows affected.');
|
|
if UpperCase(Copy(SQL, 1, 3)) = 'USE' then begin
|
|
FDatabase := Trim(Copy(SQL, 4, Length(SQL)-3));
|
|
FDatabase := DeQuoteIdent(FDatabase);
|
|
Log(lcDebug, 'Database "'+FDatabase+'" selected');
|
|
if Assigned(FOnDatabaseChanged) then
|
|
FOnDatabaseChanged(Database);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
{**
|
|
Set "Database" property and select that db if connected
|
|
}
|
|
procedure TMySQLConnection.SetDatabase(Value: WideString);
|
|
begin
|
|
if (Value = '') or (Value = FDatabase) then
|
|
Exit;
|
|
Query('USE '+QuoteIdent(Value), False);
|
|
end;
|
|
|
|
|
|
{**
|
|
Return current thread id
|
|
}
|
|
function TMySQLConnection.GetThreadId: Cardinal;
|
|
begin
|
|
Result := mysql_thread_id(FHandle);
|
|
end;
|
|
|
|
|
|
{**
|
|
Return currently used character set
|
|
}
|
|
function TMySQLConnection.GetCharacterSet: String;
|
|
begin
|
|
Result := mysql_character_set_name(FHandle);
|
|
end;
|
|
|
|
|
|
{**
|
|
Switch character set
|
|
}
|
|
procedure TMySQLConnection.SetCharacterSet(CharsetName: String);
|
|
begin
|
|
mysql_set_character_set(FHandle, PAnsiChar(CharsetName));
|
|
end;
|
|
|
|
|
|
{**
|
|
Return the last error nicely formatted
|
|
}
|
|
function TMySQLConnection.GetLastError: WideString;
|
|
var
|
|
Msg, Additional: WideString;
|
|
rx: TRegExpr;
|
|
begin
|
|
Msg := Utf8Decode(mysql_error(FHandle));
|
|
// Find "(errno: 123)" in message and add more meaningful message from perror.exe
|
|
rx := TRegExpr.Create;
|
|
rx.Expression := '.+\(errno\:\s+(\d+)\)';
|
|
if rx.Exec(Msg) then begin
|
|
Additional := MySQLErrorCodes.Values[rx.Match[1]];
|
|
if Additional <> '' then
|
|
Msg := Msg + CRLF + CRLF + Additional;
|
|
end;
|
|
rx.Free;
|
|
Result := WideFormat('SQL Error (%d): %s', [mysql_errno(FHandle), Msg]);
|
|
end;
|
|
|
|
|
|
{**
|
|
Get version string as normalized integer
|
|
"5.1.12-beta-community-123" => 50112
|
|
}
|
|
function TMySQLConnection.GetServerVersionInt: Integer;
|
|
var
|
|
i, dots: Byte;
|
|
v1, v2, v3: String;
|
|
begin
|
|
Result := -1;
|
|
|
|
dots := 0;
|
|
v1 := '';
|
|
v2 := '';
|
|
v3 := '';
|
|
for i:=1 to Length(FServerVersionUntouched) do begin
|
|
if FServerVersionUntouched[i] = '.' then begin
|
|
inc(dots);
|
|
// We expect exactly 2 dots.
|
|
if dots > 2 then
|
|
break;
|
|
end else if FServerVersionUntouched[i] in ['0'..'9'] then begin
|
|
if dots = 0 then
|
|
v1 := v1 + FServerVersionUntouched[i]
|
|
else if dots = 1 then
|
|
v2 := v2 + FServerVersionUntouched[i]
|
|
else if dots = 2 then
|
|
v3 := v3 + FServerVersionUntouched[i];
|
|
end else // Don't include potential numbers of trailing string
|
|
break;
|
|
end;
|
|
|
|
// Concat tokens
|
|
if (Length(v1)>0) and (Length(v2)>0) and (Length(v3)>0) then begin
|
|
Result := StrToIntDef(v1, 0) *10000 +
|
|
StrToIntDef(v2, 0) *100 +
|
|
StrToIntDef(v3, 0);
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetServerVersionStr: String;
|
|
begin
|
|
Result := ConvertServerVersion(ServerVersionInt);
|
|
end;
|
|
|
|
|
|
{**
|
|
Convert integer version to real version string
|
|
}
|
|
function TMySQLConnection.ConvertServerVersion(Version: Integer): String;
|
|
var
|
|
v : String;
|
|
v1, v2 : Byte;
|
|
begin
|
|
v := IntToStr( Version );
|
|
v1 := StrToIntDef( v[2]+v[3], 0 );
|
|
v2 := StrToIntDef( v[4]+v[5], 0 );
|
|
Result := v[1] + '.' + IntToStr(v1) + '.' + IntToStr(v2);
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetResults(SQL: WideString): TMySQLQuery;
|
|
begin
|
|
Result := TMySQLQuery.Create(Self);
|
|
Result.Connection := Self;
|
|
Result.SQL := SQL;
|
|
try
|
|
Result.Execute;
|
|
except
|
|
FreeAndNil(Result);
|
|
Raise;
|
|
end;
|
|
end;
|
|
|
|
|
|
{**
|
|
Call log event if assigned to object
|
|
}
|
|
procedure TMySQLConnection.Log(Category: TMySQLLogCategory; Msg: WideString);
|
|
begin
|
|
if Assigned(FOnLog) then
|
|
FOnLog(FLogPrefix+Msg, Category);
|
|
end;
|
|
|
|
|
|
{**
|
|
Escapes a string for usage in SQL queries
|
|
}
|
|
function TMySQLConnection.EscapeString(Text: WideString; DoQuote: Boolean): WideString;
|
|
var
|
|
BufferLen: Integer;
|
|
Buffer: PChar;
|
|
NativeText: String;
|
|
begin
|
|
BufferLen := Length(Text) * 2 + 1;
|
|
GetMem(Buffer, BufferLen);
|
|
NativeText := UTF8Encode(Text);
|
|
BufferLen := mysql_real_escape_string(FHandle, Buffer, PChar(NativeText), Length(Text));
|
|
SetString(Result, Buffer, BufferLen);
|
|
FreeMem(Buffer);
|
|
|
|
if DoQuote then
|
|
Result := '''' + Result + '''';
|
|
end;
|
|
|
|
|
|
{**
|
|
Add backticks to identifier
|
|
Todo: Support ANSI style
|
|
}
|
|
function TMySQLConnection.QuoteIdent(Identifier: WideString): WideString;
|
|
begin
|
|
Result := WideStringReplace(Identifier, '`', '``', [rfReplaceAll]);
|
|
Result := '`' + Result + '`';
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.DeQuoteIdent(Identifier: WideString): WideString;
|
|
begin
|
|
Result := Identifier;
|
|
if (Result[1] = '`') and (Result[Length(Identifier)] = '`') then
|
|
Result := Copy(Result, 2, Length(Result)-2);
|
|
end;
|
|
|
|
|
|
{**
|
|
Detect various capabilities of the server
|
|
for easy feature-checks in client-applications.
|
|
}
|
|
procedure TMySQLConnection.DetectCapabilities;
|
|
var
|
|
ver: Integer;
|
|
procedure addCap(c: TMySQLServerCapability; addit: Boolean);
|
|
begin
|
|
if addit then
|
|
Include(FCapabilities, c)
|
|
else
|
|
Exclude(FCapabilities, c);
|
|
end;
|
|
begin
|
|
// Avoid calling GetServerVersionInt too often
|
|
ver := ServerVersionInt;
|
|
|
|
addCap(cpShowEngines, ver >= 40102);
|
|
addCap(cpShowTableStatus, ver >= 32300);
|
|
addCap(cpShowFullTables, ver >= 50002);
|
|
addCap(cpShowCreateTable, ver >= 32320);
|
|
addCap(cpShowCreateDatabase, ver >= 50002);
|
|
addCap(cpHelpSystem, ver >= 40100);
|
|
addCap(cpSetNames, ver >= 40100);
|
|
addCap(cpCalcFoundRows, ver >= 40000);
|
|
addCap(cpLoadFile, ver >= 32206);
|
|
addCap(cpTableComment, ver >= 32300);
|
|
addCap(cpFieldComment, ver >= 40100);
|
|
addCap(cpColumnMoving, ver >= 40001);
|
|
addCap(cpTruncateTable, ver >= 50003);
|
|
addCap(cpAlterDatabase, ver >= 50002);
|
|
addCap(cpRenameDatabase, ver >= 50107);
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetCol(SQL: WideString; Column: Integer=0): TWideStringList;
|
|
var
|
|
Results: TMySQLQuery;
|
|
begin
|
|
Results := GetResults(SQL);
|
|
Result := TWideStringList.Create;
|
|
if Results.RecordCount > 0 then while not Results.Eof do begin
|
|
Result.Add(Results.Col(Column));
|
|
Results.Next;
|
|
end;
|
|
FreeAndNil(Results);
|
|
end;
|
|
|
|
|
|
{**
|
|
Get single cell value via SQL query, identified by column number
|
|
}
|
|
function TMySQLConnection.GetVar(SQL: WideString; Column: Integer=0): WideString;
|
|
var
|
|
Results: TMySQLQuery;
|
|
begin
|
|
Results := GetResults(SQL);
|
|
if Results.RecordCount > 0 then
|
|
Result := Results.Col(Column)
|
|
else
|
|
Result := '';
|
|
FreeAndNil(Results);
|
|
end;
|
|
|
|
|
|
{**
|
|
Get single cell value via SQL query, identified by column name
|
|
}
|
|
function TMySQLConnection.GetVar(SQL: WideString; Column: WideString): WideString;
|
|
var
|
|
Results: TMySQLQuery;
|
|
begin
|
|
Results := GetResults(SQL);
|
|
if Results.RecordCount > 0 then
|
|
Result := Results.Col(Column)
|
|
else
|
|
Result := '';
|
|
FreeAndNil(Results);
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetTableEngines: TStringList;
|
|
var
|
|
ShowEngines, HaveEngines: TMySQLQuery;
|
|
engineName, engineSupport: String;
|
|
PossibleEngines: TStringList;
|
|
begin
|
|
if not Assigned(FTableEngines) then begin
|
|
FTableEngines := TStringList.Create;
|
|
try
|
|
ShowEngines := GetResults('SHOW ENGINES');
|
|
while not ShowEngines.Eof do begin
|
|
engineName := ShowEngines.Col('Engine');
|
|
engineSupport := LowerCase(ShowEngines.Col('Support'));
|
|
// Add to dropdown if supported
|
|
if engineSupport <> 'no' then
|
|
FTableEngines.Add(engineName);
|
|
// Check if this is the default engine
|
|
if engineSupport = 'default' then
|
|
FTableEngineDefault := engineName;
|
|
ShowEngines.Next;
|
|
end;
|
|
except
|
|
// Ignore errors on old servers and try a fallback:
|
|
// Manually fetch available engine types by analysing have_* options
|
|
// This is for servers below 4.1 or when the SHOW ENGINES statement has
|
|
// failed for some other reason
|
|
HaveEngines := GetResults('SHOW VARIABLES LIKE ''have%''');
|
|
// Add default engines which will not show in a have_* variable:
|
|
FTableEngines.CommaText := 'MyISAM,MRG_MyISAM,HEAP';
|
|
FTableEngineDefault := 'MyISAM';
|
|
// Possible other engines:
|
|
PossibleEngines := TStringList.Create;
|
|
PossibleEngines.CommaText := 'ARCHIVE,BDB,BLACKHOLE,CSV,EXAMPLE,FEDERATED,INNODB,ISAM';
|
|
while not HaveEngines.Eof do begin
|
|
engineName := copy(HaveEngines.Col(0), 6, Length(HaveEngines.Col(0)));
|
|
// Strip additional "_engine" suffix, fx from "have_blackhole_engine"
|
|
if Pos('_', engineName) > 0 then
|
|
engineName := copy(engineName, 0, Pos('_', engineName)-1);
|
|
engineName := UpperCase(engineName);
|
|
// Add engine to list if it's a) in HaveEngineList and b) activated
|
|
if (PossibleEngines.IndexOf(engineName) > -1)
|
|
and (LowerCase(HaveEngines.Col(1)) = 'yes') then
|
|
FTableEngines.Add(engineName);
|
|
HaveEngines.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
Result := FTableEngines;
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetCollationTable: TMySQLQuery;
|
|
begin
|
|
if (not Assigned(FCollationTable)) and (ServerVersionInt >= 40100) then
|
|
FCollationTable := GetResults('SHOW COLLATION');
|
|
if Assigned(FCollationTable) then
|
|
FCollationTable.First;
|
|
Result := FCollationTable;
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetCollationList: TStringList;
|
|
var
|
|
c: TMySQLQuery;
|
|
begin
|
|
c := CollationTable;
|
|
Result := TStringList.Create;
|
|
if Assigned(c) then while not c.Eof do begin
|
|
Result.Add(c.Col('Collation'));
|
|
c.Next;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetCharsetTable: TMySQLQuery;
|
|
begin
|
|
if (not Assigned(FCharsetTable)) and (ServerVersionInt >= 40100) then
|
|
FCharsetTable := GetResults('SHOW CHARSET');
|
|
Result := FCharsetTable;
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetCharsetList: TStringList;
|
|
var
|
|
c: TMySQLQuery;
|
|
begin
|
|
c := CharsetTable;
|
|
Result := TStringList.Create;
|
|
if Assigned(c) then begin
|
|
c.First;
|
|
while not c.Eof do begin
|
|
Result.Add(c.Col('Description') + ' (' + c.Col('Charset') + ')');
|
|
c.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetInformationSchemaObjects: TWideStringList;
|
|
begin
|
|
if not Assigned(FInformationSchemaObjects) then try
|
|
FInformationSchemaObjects := GetCol('SHOW TABLES FROM '+QuoteIdent('information_schema'));
|
|
except
|
|
// Gracefully return an empty list on old servers
|
|
FInformationSchemaObjects := TWideStringlist.Create;
|
|
end;
|
|
Result := FInformationSchemaObjects;
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetConnectionUptime: Integer;
|
|
begin
|
|
// Return seconds since last connect
|
|
if not FActive then
|
|
Result := 0
|
|
else
|
|
Result := Integer(GetTickCount div 1000) - FConnectionStarted;
|
|
end;
|
|
|
|
|
|
function TMySQLConnection.GetServerUptime: Integer;
|
|
begin
|
|
// Return server uptime in seconds
|
|
Result := Integer(GetTickCount div 1000) - FServerStarted;
|
|
end;
|
|
|
|
|
|
procedure TMySQLConnection.ClearCache;
|
|
begin
|
|
// Free cached lists and results. Called when the connection was closed and/or destroyed
|
|
FreeAndNil(FCollationTable);
|
|
FreeAndNil(FCharsetTable);
|
|
FreeAndNil(FTableEngines);
|
|
FreeAndNil(FInformationSchemaObjects);
|
|
FTableEngineDefault := '';
|
|
end;
|
|
|
|
|
|
|
|
{ TMySQLQuery }
|
|
|
|
constructor TMySQLQuery.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FRecNo := -1;
|
|
FRecordCount := 0;
|
|
FColumnNames := TWideStringlist.Create;
|
|
FColumnNames.CaseSensitive := True;
|
|
end;
|
|
|
|
|
|
destructor TMySQLQuery.Destroy;
|
|
begin
|
|
FreeAndNil(FColumnNames);
|
|
if HasResult then
|
|
mysql_free_result(FLastResult);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
procedure TMySQLQuery.SetSQL(Value: WideString);
|
|
begin
|
|
FSQL := Value;
|
|
end;
|
|
|
|
|
|
procedure TMySQLQuery.Execute;
|
|
var
|
|
i, j, NumFields: Integer;
|
|
Field: PMYSQL_FIELD;
|
|
IsBinary: Boolean;
|
|
begin
|
|
FLastResult := Connection.Query(FSQL, True);
|
|
FRecordCount := Connection.RowsFound;
|
|
if HasResult then begin
|
|
NumFields := mysql_num_fields(FLastResult);
|
|
SetLength(FDatatypes, NumFields);
|
|
for i:=0 to NumFields-1 do begin
|
|
Field := mysql_fetch_field_direct(FLastResult, i);
|
|
FColumnNames.Add(Utf8Decode(Field.name));
|
|
|
|
FDatatypes[i] := Datatypes[Low(Datatypes)];
|
|
if (Field.flags and ENUM_FLAG) = ENUM_FLAG then
|
|
FDatatypes[i] := Datatypes[Integer(dtEnum)]
|
|
else if (Field.flags and SET_FLAG) = SET_FLAG then
|
|
FDatatypes[i] := Datatypes[Integer(dtSet)]
|
|
else for j:=Low(Datatypes) to High(Datatypes) do begin
|
|
if Field._type = Datatypes[j].NativeType then begin
|
|
// Text and Blob types share the same constants (see FIELD_TYPEs in mysql_api)
|
|
// Some function results return binary collation up to the latest versions. Work around
|
|
// that by checking if this field is a real table field
|
|
// See http://bugs.mysql.com/bug.php?id=10201
|
|
if Connection.IsUnicode then
|
|
IsBinary := (Field.charsetnr = COLLATION_BINARY) and (Field.org_table <> '')
|
|
else
|
|
IsBinary := (Field.flags and BINARY_FLAG) = BINARY_FLAG;
|
|
if IsBinary and (Datatypes[j].Category = dtcText) then
|
|
continue;
|
|
FDatatypes[i] := Datatypes[j];
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
RecNo := 0;
|
|
end else
|
|
SetLength(FDatatypes, 0);
|
|
end;
|
|
|
|
|
|
procedure TMySQLQuery.First;
|
|
begin
|
|
RecNo := 0;
|
|
end;
|
|
|
|
|
|
procedure TMySQLQuery.Next;
|
|
begin
|
|
RecNo := RecNo + 1;
|
|
end;
|
|
|
|
|
|
procedure TMySQLQuery.SetRecNo(Value: Int64);
|
|
begin
|
|
if Value >= RecordCount then begin
|
|
FRecNo := RecordCount;
|
|
FEof := True;
|
|
end else begin
|
|
if FRecNo+1 <> Value then
|
|
mysql_data_seek(FLastResult, Value);
|
|
FRecNo := Value;
|
|
FEof := False;
|
|
FCurrentRow := mysql_fetch_row(FLastResult);
|
|
end;
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.ColumnCount: Integer;
|
|
begin
|
|
Result := ColumnNames.Count;
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.Col(Column: Integer; IgnoreErrors: Boolean=False): WideString;
|
|
var
|
|
LengthPointer: PLongInt;
|
|
BinLen: LongInt;
|
|
Bin: String;
|
|
begin
|
|
if (Column > -1) and (Column < ColumnCount) then begin
|
|
if FDatatypes[Column].Category = dtcBinary then begin
|
|
LengthPointer := mysql_fetch_lengths(FLastResult);
|
|
if LengthPointer <> nil then begin
|
|
BinLen := PLongInt(LongInt(LengthPointer) + Column * SizeOf(LongInt))^;
|
|
SetString(Bin, FCurrentRow[Column], BinLen);
|
|
Result := WideString(Bin);
|
|
end;
|
|
end else begin
|
|
if Connection.IsUnicode then
|
|
Result := UTF8StringToWideString(FCurrentRow[Column])
|
|
else
|
|
Result := FCurrentRow[Column];
|
|
end;
|
|
end else if not IgnoreErrors then
|
|
Raise Exception.CreateFmt('Column #%d not available. Query returned %d columns and %d rows.', [Column, ColumnCount, RecordCount]);
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.Col(ColumnName: WideString; IgnoreErrors: Boolean=False): WideString;
|
|
var
|
|
idx: Integer;
|
|
begin
|
|
idx := ColumnNames.IndexOf(ColumnName);
|
|
if idx > -1 then
|
|
Result := Col(idx)
|
|
else if not IgnoreErrors then
|
|
Raise Exception.CreateFmt('Column "%s" not available.', [ColumnName]);
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.DataType(Column: Integer): TDataType;
|
|
begin
|
|
Result := FDatatypes[Column];
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.ColExists(Column: WideString): Boolean;
|
|
begin
|
|
Result := (ColumnNames <> nil) and (ColumnNames.IndexOf(Column) > -1);
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.ColIsPrimaryKeyPart(Column: Integer): Boolean;
|
|
var
|
|
Field: PMYSQL_FIELD;
|
|
begin
|
|
if HasResult and (Column < ColumnCount) then begin
|
|
Field := mysql_fetch_field_direct(FLastResult, Column);
|
|
Result := (Field.flags and PRI_KEY_FLAG) = PRI_KEY_FLAG;
|
|
end else
|
|
Result := False;
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.IsNull(Column: Integer): Boolean;
|
|
begin
|
|
Result := FCurrentRow[Column] = nil;
|
|
end;
|
|
|
|
|
|
function TMySQLQuery.HasResult: Boolean;
|
|
begin
|
|
Result := FLastResult <> nil;
|
|
end;
|
|
|
|
|
|
end.
|