diff --git a/extra/mysql_dataset/mysqldataset.pas b/extra/mysql_dataset/mysqldataset.pas deleted file mode 100644 index c94eefb9..00000000 --- a/extra/mysql_dataset/mysqldataset.pas +++ /dev/null @@ -1,1077 +0,0 @@ -unit mysqldataset; - -{$M+} // Needed to add published properties - -interface - -uses - Classes, db, SysUtils, windows, mysql_api; - -type - - { **************** } - { TMySQLConnection } - { **************** } - - TMySQLLogCategory = (lcStats, lcSQL, lcError, lcInternal); - TMySQLLogEvent = procedure (Category: TMySQLLogCategory; Msg: String) 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 - cpBackticks, // `identifier` - 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]; - -type - TMySQLConnection = class(TComponent) - private - FHandle: PMYSQL; - FActive: Boolean; - FHostname: String; - FPort: Cardinal; - FUsername: String; - FPassword: String; - FTimeout: Cardinal; - FDatabase: String; - FOnLog: TMySQLLogEvent; - FOptions: TMySQLClientOptions; - FCapabilities: TMySQLServerCapabilities; - FRowsFound: Int64; - FRowsAffected: Int64; - procedure SetActive( Value: Boolean ); - procedure SetDatabase( Value: String ); - function GetThreadId: Cardinal; - function GetCharacterSet: String; - function GetLastError: String; - function GetServerVersionStr: String; - function GetServerVersionInt: Integer; - procedure Log(Category: TMySQLLogCategory; Msg: String); - procedure DetectCapabilities; - public - constructor Create(AOwner: TComponent); override; - function Query( SQL: String ): PMYSQL_RES; - function EscapeString( Text: String; DoQuote: Boolean = True ): String; - function QuoteIdent( Identifier: String ): String; - function GetRow( SQL: String; RowOffset: Int64 = 0 ): TStringList; - function GetVar( SQL: String; Column: Integer = 0 ): String; overload; - function GetVar( SQL: String; Column: String ): String; overload; - - property ThreadId: Cardinal read GetThreadId; - property Handle: PMYSQL read FHandle; - property CharacterSet: String read GetCharacterSet; - property LastError: String read GetLastError; - 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; - - published - property Active: Boolean read FActive write SetActive default False; - property Hostname: String read FHostname write FHostname; - property Port: Cardinal read FPort write FPort default MYSQL_PORT; - property Username: String read FUsername write FUsername; - property Password: String read FPassword write FPassword; - property Timeout: Cardinal read FTimeout write FTimeout default NET_READ_TIMEOUT; - property Database: String read FDatabase write SetDatabase; - property Options: TMySQLClientOptions read FOptions write FOptions default DEFAULT_MYSQLOPTIONS; - - // Events - property OnLog: TMySQLLogEvent read FOnLog write FOnLog; - end; - - - { *********** } - { TMySQLQuery } - { *********** } - - TMySQLQuery = class(TDataSet) - private - FSQL: TStrings; - FConnection: TMySQLConnection; - FRowsAffected: Int64; - FLastResult: PMYSQL_RES; - FRecNo: Int64; - FCurrentRow: PMYSQL_ROW; - procedure SetQuery(Value: TStrings); - protected - function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override; - procedure SetFieldData(Field: TField; Buffer: Pointer); override; - function GetRecordSize: Word; override; - function GetCanModify: Boolean; override; - procedure InternalOpen; override; - procedure InternalClose; override; - procedure InternalInitFieldDefs; override; - procedure InternalHandleException; override; - procedure InternalInitRecord(Buffer: PChar); override; - function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override; - procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override; - procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override; - procedure InternalSetToRecord(Buffer: PChar); override; - function IsCursorOpen: Boolean; override; - procedure InternalFirst; override; - procedure InternalLast; override; - procedure InternalEdit; override; - procedure InternalInsert; override; - procedure InternalPost; override; - procedure InternalDelete; override; - function GetRecNo: Integer; override; - function GetRecordCount: Integer; override; - procedure SetRecNo(Value: Integer); override; - - public - constructor Create(AOwner: TComponent); override; - procedure ExecSQL; - property RowsAffected: Int64 read FRowsAffected; - function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override; - published - property SQL: TStrings read FSQL write SetQuery; - property Connection: TMySQLConnection read FConnection write FConnection; - - property AutoCalcFields; - property BeforeOpen; - property AfterOpen; - property BeforeClose; - property AfterClose; - property BeforeRefresh; - property AfterRefresh; - property BeforeScroll; - property AfterScroll; - property OnCalcFields; - property OnFilterRecord; - property Filter; - property Filtered; - end; - - procedure Register; - - // Should be removed when this baby is running - procedure debug(txt: String); - - -implementation - - -procedure debug(txt: String); -begin - txt := 'mds '+txt; - OutputDebugString(PChar(txt)); -end; - -procedure Register; -begin - RegisterComponents('MySQL Dataset', [TMySQLConnection, TMySQLQuery]); -end; - - - -{ **************** } -{ TMySQLConnection } -{ **************** } - -constructor TMySQLConnection.Create(AOwner: TComponent); -begin - inherited Create(AOwner); - FOptions := DEFAULT_MYSQLOPTIONS; - FTimeout := NET_READ_TIMEOUT; - FPort := MYSQL_PORT; - FRowsFound := -1; - FRowsAffected := -1; -end; - - -{** - (Dis-)Connect to/from server - Parts copied from MySQL-Front or revision 1 of HeidiSQL's childwin.pas -} -procedure TMySQLConnection.SetActive( Value: Boolean ); -var - connected : PMYSQL; - ClientFlags : Integer; - error : String; -begin - FActive := Value; - - if Value and (FHandle = nil) then - begin - // Get handle - FHandle := mysql_init(nil); - // timeout - mysql_options(FHandle, MYSQL_OPT_CONNECT_TIMEOUT, @FTimeout); - - // read ini-file - // mysql_options(FHandle, MYSQL_READ_DEFAULT_FILE, pchar(ExtractFilePath(paramstr(0)) + 'my.ini')); - // read [Client]-section from ini-file - mysql_options(FHandle, MYSQL_READ_DEFAULT_GROUP, pchar('Client')); - - // 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; - - // Connect - connected := mysql_real_connect(FHandle, - pChar(FHostname), - pChar(FUsername), - pChar(FPassword), - nil, - FPort, - nil, - ClientFlags - ); - if connected = nil then - begin - error := LastError; - Log( lcError, error ); - FActive := False; - FHandle := nil; - raise Exception.Create( error ); - end - else begin - DetectCapabilities; - Log( lcStats, 'Connection established with host "'+Hostname+'" on port '+IntToStr(Port)+' as user "'+Username+'"' ); - Log( lcStats, 'Connection-ID: '+IntToStr(ThreadId) ); - Log( lcStats, 'Characterset: '+CharacterSet ); - Log( lcStats, 'Server version: '+ServerVersionStr+' ('+IntToStr(ServerVersionInt)+')' ); - SetDatabase( FDatabase ); - end; - end - - else if (not Value) and (FHandle <> nil) then - begin - mysql_close(FHandle); - FHandle := nil; - FCapabilities := []; - Log( lcStats, 'Connection closed' ); - end; - -end; - - -{** - Executes a query -} -function TMySQLConnection.Query(SQL: string): PMYSQL_RES; -var - querystatus : Integer; -begin - if Not FActive then - SetActive( True ); - Log( lcSQL, Trim(Copy(SQL, 1, 1024)) ); - querystatus := mysql_real_query(FHandle, pChar(SQL), length(SQL)); - if querystatus <> 0 then - begin - Log( lcError, GetLastError ); - raise Exception.Create(GetLastError); - end - else begin - FRowsAffected := mysql_affected_rows( FHandle ); - Log( lcStats, IntToStr(RowsAffected)+' rows affected.' ); - Result := mysql_store_result( FHandle ); - try - FRowsFound := mysql_num_rows( Result ); - Log( lcStats, IntToStr(RowsFound)+' rows found.' ); - except - // mysql_num_rows caused an exception. - // Now we know that the query was a non-result-query. - Log( lcInternal, 'Query returned empty resultset.' ); - mysql_free_result( Result ); - Result := nil; - end; - end; -end; - - -{** - Set "Database" property and select that db if connected -} -procedure TMySQLConnection.SetDatabase( Value: String ); -var - oldValue : String; -begin - if Value = '' then - Exit; - - oldValue := FDatabase; - FDatabase := Value; - - // Switch to DB if connected. - // If not connected, SetDatabase() should be called by SetActive() - if FActive then - try - Query( 'USE '+QuoteIdent(Value) ); - Log( lcStats, 'Database "'+Value+'" selected' ) - except - On E:Exception do - begin - FDatabase := oldValue; - raise Exception.Create(e.Message); - end; - end; -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; - - -{** - Return the last error nicely formatted -} -function TMySQLConnection.GetLastError: String; -begin - Result := Format('SQL Error (%d): %s', [mysql_errno(FHandle), mysql_error(FHandle)] ); -end; - - -{** - Return the untouched server version string -} -function TMySQLConnection.GetServerVersionStr: String; -begin - Result := mysql_get_server_info(FHandle); -end; - - -{** - Get version string as normalized integer - "5.1.12-beta-community-123" => 50112 -} -function TMySQLConnection.GetServerVersionInt: Integer; -var - i, dots: Byte; - fullversion, v1, v2, v3: String; -begin - Result := -1; - - dots := 0; - // Avoid calling GetServerVersionStr too often - fullversion := ServerVersionStr; - v1 := ''; - v2 := ''; - v3 := ''; - for i := 1 to Length(fullversion) do - begin - if fullversion[i] = '.' then - begin - inc(dots); - // We expect exactly 2 dots. - if dots > 2 then - break; - end - else if fullversion[i] in ['0'..'9'] then - begin - if dots = 0 then - v1 := v1 + fullversion[i] - else if dots = 1 then - v2 := v2 + fullversion[i] - else if dots = 2 then - v3 := v3 + fullversion[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; - - -{** - Call log event if assigned to object -} -procedure TMySQLConnection.Log(Category: TMySQLLogCategory; Msg: String); -begin - if Assigned(FOnLog) then - FOnLog( Category, Msg); -end; - - -{** - Escapes a string for usage in SQL queries -} -function TMySQLConnection.EscapeString( Text: String; DoQuote: Boolean ): String; -var - BufferLen: Integer; - Buffer: PChar; -begin - BufferLen := Length(Text) * 2 + 1; - GetMem(Buffer, BufferLen); - BufferLen := mysql_real_escape_string(FHandle, Buffer, PChar(Text), 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: String ): String; -begin - if cpBackticks in Capabilities then - begin - Result := StringReplace(Identifier, '`', '``', [rfReplaceAll]); - Result := '`' + Result + '`'; - end - else - Result := Identifier; -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( cpBackticks, ver >= 32300 ); - addCap( cpAlterDatabase, ver >= 50002 ); - addCap( cpRenameDatabase, ver >= 50107 ); - -end; - - -{** - Get one row via SQL query as TStringList - Todo: free res after raising exception -} -function TMySQLConnection.GetRow( SQL: String; RowOffset: Int64 = 0 ): TStringList; -var - res : PMYSQL_RES; - row : PMYSQL_ROW; - field : PMYSQL_FIELD; - i : Integer; -begin - Result := TStringList.Create; - res := Query( SQL ); - if RowsFound < RowOffset+1 then - raise Exception.Create( 'Error ('+Self.ClassName+'): Query returned not enough rows: '+IntToStr(RowsFound)+', wanted offset: '+IntToStr(RowOffset) ); - mysql_data_seek( res, RowOffset ); - row := mysql_fetch_row( res ); - for i := 0 to mysql_num_fields(res) - 1 do - begin - field := mysql_fetch_field_direct( res, i ); - Result.Values[field.name] := row[i]; - end; - mysql_free_result(res); -end; - - -{** - Get single cell value via SQL query, identified by column number - Todo: free row after raising exception -} -function TMySQLConnection.GetVar( SQL: String; Column: Integer = 0 ): String; -var - row : TStringList; -begin - row := GetRow( SQL ); - if row.Count < Column+1 then - raise Exception.Create( 'Error ('+Self.ClassName+'): Fetching field nr. '+IntToStr(Column)+' not possible - query returned '+IntToStr(row.Count)+' fields.' ); - Result := row.ValueFromIndex[Column]; - FreeAndNil(row); -end; - - -{** - Get single cell value via SQL query, identified by column name - Todo: free row after raising exception -} -function TMySQLConnection.GetVar( SQL: String; Column: String ): String; -var - row : TStringList; - i : Integer; - colexists : Boolean; -begin - row := GetRow( SQL ); - colexists := False; - for i := 0 to row.Count - 1 do - begin - if LowerCase(row.Names[i]) = LowerCase(Column) then - begin - colexists := True; - break; - end; - end; - if not colexists then - raise Exception.Create( 'Error ('+Self.ClassName+'): Field "'+Column+'" does not exist in resultset.' ); - Result := row.Values[Column]; - FreeAndNil(row); -end; - - - - -{ *********** } -{ TMySQLQuery } -{ *********** } - -constructor TMySQLQuery.Create(AOwner: TComponent); -begin - inherited Create(AOwner); - FSQL := TStringList.Create; - FRowsAffected := -1; - FRecNo := -1; -end; - - -{** - Executes a query without handling the resultset -} -procedure TMySQLQuery.ExecSQL; -var - res: PMYSQL_RES; -begin - res := FConnection.Query( FSQL.Text ); - // Important to temporary store the number of affected rows now - // because the connection object is free to execute further queries now. - FRowsAffected := Connection.RowsAffected; - // Free result, we don't do anything with it in ExecSQL - mysql_free_result( res ); -end; - - -{** - Set SQL TStringList -} -procedure TMySQLQuery.SetQuery(Value: TStrings); -begin - if FSQL.Text <> Value.Text then - begin - FSQL.BeginUpdate; - try - FSQL.Assign(Value); - finally - FSQL.EndUpdate; - end; - end; -end; - - -{** - The most important method for a TDataset: - Navigate to and fetch the current, next or prior row -} -function TMySQLQuery.GetRecord(Buffer: PChar; GetMode: TGetMode; - DoCheck: Boolean): TGetResult; -begin - Result := grOK; - - case GetMode of - gmCurrent: - begin - if (RecNo < 0) or (RecNo >= RecordCount) then - Result := grError - else - Result := grOK; - end; - - gmNext: - if RecNo >= RecordCount then - Result := grEOF - else - begin - RecNo := RecNo + 1; - Result := grOK; - end; - - gmPrior: - if RecNo <= 0 then - Result := grBOF - else - begin - RecNo := RecNo - 1; - Result := grOK; - end; - end; - - if Result = grOK then - begin - FCurrentRow := mysql_fetch_row( FLastResult ); - System.Move( - FCurrentRow, - Buffer, - SizeOf(FCurrentRow) - ); - end; - -end; - -function TMySQLQuery.GetFieldData(Field: TField; Buffer: Pointer): Boolean; -begin - { - The Field parameter is the field for which the value needs to be retrieved. - The Field parameter is only passed for reference and should never be altered by this routine. - - The Buffer parameter is where the field value needs to be copied to. - Looking at the buffer parameter results in a question that doesn't have an - obvious answer at first glance. That question is "What size is that buffer - and what needs to be copied into it?". The only way of determining this is - by looking at the various TField types in DB.pas and examining their GetValue - and GetDataSize methods. - - Here is a partial table with some values used in the base dataset we will create later on: - Field Type Buffer Result - ftInteger,ftDate,ftTime Integer - ftBoolean Boolean - ftDateTime TDateTimeRec - ftFloat,ftCurrency Double - ftString PChar - - As we can see, most types map pretty cleanly with the noteable exception of TDateTime - which requires some translation into a TDateTimeRec. - - GetFieldData function returns True if a value was copied into the buffer by the - method and False if no value was copied. - - That covers the GetFieldData method. - } - FConnection.Log(lcInternal, 'GetFieldData called for RecNo '+inttostr(RecNo)+' field "'+Field.Name+'" ('+inttostr(field.FieldNo)+')'); - if Field.DataType = ftString then - begin - System.Move( - FCurrentRow[Field.FieldNo-1]^, - //ZEOS: RowAccessor.GetColumnData(ColumnIndex, Result)^, - Buffer, - //ZEOS: RowAccessor.GetColumnDataSize(ColumnIndex) - SizeOf(FCurrentRow[Field.FieldNo-1]) - ); - Result := True; - end - else - Result := False; -end; - - -{** - Tell dataset the contents of a field -} -procedure TMySQLQuery.SetFieldData(Field: TField; Buffer: Pointer); -begin - { SetFieldData is the exact reverse operation - of GetFieldData. It is passed a buffer with some field value in the buffer that - must then be copied back into your record buffer. - } - if Field.DataType = ftString then - begin - System.Move( - Buffer^, - FCurrentRow, - SizeOf(FCurrentRow) - ); - end; -end; - - -{** - ?? -} -function TMySQLQuery.GetRecordSize: Word; -begin - Result := 1; -end; - - -{** - Dataset is editable? - Should be True for simple SELECTs and False for not parsable SELECTs -} -function TMySQLQuery.GetCanModify: Boolean; -begin - Result := True; -end; - - -{** - Send query and fetch resultset -} -procedure TMySQLQuery.InternalOpen; -begin - FLastResult := FConnection.Query( FSQL.Text ); - FieldDefs.Clear; - FieldDefs.Update; // Calls InternalInitFieldDefs - - if DefaultFields then CreateFields; - BindFields(True); -end; - - -{** - Close resultset -} -procedure TMySQLQuery.InternalClose; -begin - mysql_free_result(FLastResult); -end; - - -{** - Fetch field types of recent resultset -} -procedure TMySQLQuery.InternalInitFieldDefs; -var - def: TFieldDef; - i, numfields: Cardinal; - field: PMYSQL_FIELD; - fType: TFieldType; - - // Detect signed flag of a field - function Signed: Boolean; - begin - Result := (UNSIGNED_FLAG and field.flags) = 0; - end; - -begin - numfields := mysql_num_fields(FLastResult); - - for i := 0 to numfields-1 do - begin - field := mysql_fetch_field_direct(FLastResult, i); - - // Create a new field - def := FieldDefs.AddFieldDef; - def.FieldNo := i; - def.Name := field.name; - - // Map field type to delphi-types - // see TFieldType in DB.pas - case field._type of - FIELD_TYPE_TINY: - fType := ftSmallint; - - FIELD_TYPE_YEAR, FIELD_TYPE_SHORT: - fType := ftInteger; - - FIELD_TYPE_INT24, FIELD_TYPE_LONG: - begin - if Signed then - fType := ftInteger - else - fType := ftFloat; - end; - - FIELD_TYPE_LONGLONG: - fType := ftString; - - FIELD_TYPE_FLOAT: - fType := ftFloat; - - FIELD_TYPE_DECIMAL, FIELD_TYPE_NEWDECIMAL: - begin - if (field.decimals = 0) and (field.length < 11) then - fType := ftInteger - else - fType := ftFloat; - end; - - FIELD_TYPE_DOUBLE: - fType := ftFloat; - - FIELD_TYPE_DATE: - fType := ftString; - - FIELD_TYPE_TIME: - fType := ftString; - - FIELD_TYPE_DATETIME, FIELD_TYPE_TIMESTAMP: - fType := ftString; - - FIELD_TYPE_TINY_BLOB, FIELD_TYPE_MEDIUM_BLOB, - FIELD_TYPE_LONG_BLOB, FIELD_TYPE_BLOB: - if (field.flags and BINARY_FLAG) = 0 then - fType := ftMemo - else - fType := ftBlob; - - FIELD_TYPE_BIT: - fType := ftBlob; - - FIELD_TYPE_VARCHAR: - fType := ftString; - - FIELD_TYPE_VAR_STRING: - fType := ftString; - - FIELD_TYPE_STRING: - fType := ftString; - - FIELD_TYPE_ENUM: - fType := ftString; - - FIELD_TYPE_SET: - fType := ftString; - - FIELD_TYPE_NULL: - // Example: SELECT NULL FROM DUAL - // Todo: Find out if it is possible to get real data in a - // TYPE_NULL field, perhaps adjust to binary or some such? - fType := ftString; - - FIELD_TYPE_GEOMETRY: - // Todo: Would be nice to show as WKT. - fType := ftBlob; - - else - raise Exception.Create('Unknown MySQL data type!'+IntToStr(field._type)); - end; - - def.DataType := fType; - if fType in [ftString, ftWidestring, ftBytes] then - def.Size := field.length - else - def.Size := 0; - - def.Required := (field.flags and NOT_NULL_FLAG) = NOT_NULL_FLAG; - def.Precision := field.length; - - end; -end; - - -procedure TMySQLQuery.InternalHandleException; -begin - // Application.HandleException(Self); ? -end; - - -{** - ?? -} -procedure TMySQLQuery.InternalInitRecord(Buffer: PChar); -begin -end; - - -{** - ?? -} -function TMySQLQuery.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; -begin -end; - - -{** - ?? -} -procedure TMySQLQuery.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); -begin -end; - - -{** - ?? -} -procedure TMySQLQuery.GetBookmarkData(Buffer: PChar; Data: Pointer); -begin -end; - - -{** - ?? -} -procedure TMySQLQuery.InternalSetToRecord(Buffer: PChar); -begin -end; - - -{** - ?? -} -function TMySQLQuery.IsCursorOpen: Boolean; -begin - // Result := Handle <> nil; ? -end; - - -{** - Called by DataSet.First -} -procedure TMySQLQuery.InternalFirst; -begin - FRecNo := 0; - mysql_data_seek( FLastResult, FRecNo ); -end; - - -{** - Called by DataSet.Last -} -procedure TMySQLQuery.InternalLast; -begin - FRecNo := mysql_num_rows( FLastResult )-1; - mysql_data_seek( FLastResult, FRecNo ); -end; - - -{** - ?? -} -procedure TMySQLQuery.InternalEdit; -begin -end; - - -{** - Fill default values? -} -procedure TMySQLQuery.InternalInsert; -begin -end; - - -{** - Generate UPDATE or INSERT statement? -} -procedure TMySQLQuery.InternalPost; -begin - inherited; -end; - - -{** - Generate DELETE statement? -} -procedure TMySQLQuery.InternalDelete; -begin -end; - - -{** - Called by DataSet.RecNo -} -function TMySQLQuery.GetRecNo: Integer; -begin - Result := FRecNo; -end; - - -{** - Called by DataSet.RecordCount -} -function TMySQLQuery.GetRecordCount: Integer; -begin - Result := mysql_num_rows( FLastResult ); -end; - - -{** - Navigate to record -} -procedure TMySQLQuery.SetRecNo(Value: Integer); -begin - if Value > RecordCount then - Value := RecordCount-1; - FRecNo := Value; - mysql_data_seek( FLastResult, FRecNo ); -end; - - - -end. diff --git a/packages/delphi11/heidisql.dpr b/packages/delphi11/heidisql.dpr index 58793d58..9fff450f 100644 --- a/packages/delphi11/heidisql.dpr +++ b/packages/delphi11/heidisql.dpr @@ -17,14 +17,7 @@ uses insertfiles in '..\..\source\insertfiles.pas' {frmInsertFiles}, insertfiles_progress in '..\..\source\insertfiles_progress.pas' {frmInsertFilesProgress}, helpers in '..\..\source\helpers.pas', - synchronization in '..\..\source\synchronization.pas', - communication in '..\..\source\communication.pas', - threading in '..\..\source\threading.pas', sqlhelp in '..\..\source\sqlhelp.pas' {frmSQLhelp}, - queryprogress in '..\..\source\queryprogress.pas' {frmQueryProgress}, - mysqlquery in '..\..\source\mysqlquery.pas', - mysqlquerythread in '..\..\source\mysqlquerythread.pas', - mysqlconn in '..\..\source\mysqlconn.pas', mysql_structures in '..\..\source\mysql_structures.pas', column_selection in '..\..\source\column_selection.pas' {ColumnSelectionForm}, data_sorting in '..\..\source\data_sorting.pas' {DataSortingForm}, @@ -40,7 +33,9 @@ uses uVistaFuncs in '..\..\source\uVistaFuncs.pas', dataviewsave in '..\..\source\dataviewsave.pas' {FrmDataViewSave}, routine_editor in '..\..\source\routine_editor.pas' {frmRoutineEditor}, - table_editor in '..\..\source\table_editor.pas' {frmTableEditor}; + table_editor in '..\..\source\table_editor.pas' {frmTableEditor}, + mysql_api in '..\..\source\mysql_api.pas', + mysql_connection in '..\..\source\mysql_connection.pas'; {$R ..\..\res\icon.RES} {$R ..\..\res\version.RES} @@ -54,26 +49,6 @@ begin Application.CreateForm(TMainForm, MainForm); Application.OnMessage := Mainform.OnMessageHandler; debug('perf: Main created.'); - - try - try - InitializeSync(MainForm.Handle); - SetWindowName(main.discname); - InitializeThreading(MainForm.Handle); - InitializeComm( - MainForm.Handle, - MainForm.ExecuteRemoteNonQuery, - MainForm.ExecuteRemoteQuery - ); - debug('perf: Running.'); - MainForm.Startup; - Application.Run; - finally - DeInitializeSync; - end; - except - on e: Exception do begin - ShowMessage(e.ClassName + ': ' + e.Message); - end; - end; + MainForm.Startup; + Application.Run; end. diff --git a/packages/delphi11/heidisql.dproj b/packages/delphi11/heidisql.dproj index ee40dad3..235b3d84 100644 --- a/packages/delphi11/heidisql.dproj +++ b/packages/delphi11/heidisql.dproj @@ -155,10 +155,9 @@
MainForm
+ + - - -
frmTableTools
@@ -168,9 +167,6 @@
printlistForm
- -
frmQueryProgress
-
frmRoutineEditor
@@ -183,7 +179,6 @@
frmSQLhelp
-
frmTableEditor
diff --git a/source/column_selection.pas b/source/column_selection.pas index 94af9acb..003c9c27 100644 --- a/source/column_selection.pas +++ b/source/column_selection.pas @@ -4,7 +4,7 @@ interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, - Dialogs, StdCtrls, CheckLst, ExtCtrls, TntCheckLst, WideStrings, DB; + Dialogs, StdCtrls, CheckLst, ExtCtrls, TntCheckLst, WideStrings, mysql_connection; type TColumnSelectionForm = class(TForm) @@ -52,13 +52,13 @@ end; } procedure TColumnSelectionForm.FormShow(Sender: TObject); var - ds: TDataSet; + Results: TMySQLQuery; begin - ds := Mainform.SelectedTableColumns; - ds.First; - while not ds.Eof do begin - chklistColumns.Items.Add(ds.Fields[0].AsWideString); - ds.Next; + Results := Mainform.SelectedTableColumns; + Results.First; + while not Results.Eof do begin + chklistColumns.Items.Add(Results.Col(0)); + Results.Next; end; // Check items! @@ -153,7 +153,7 @@ procedure TColumnSelectionForm.chkSortClick(Sender: TObject); var checkedfields : TStringList; i: Integer; - ds: TDataSet; + Results: TMySQLQuery; begin // Memorize checked items in a list checkedfields := TStringList.Create; @@ -170,11 +170,11 @@ begin // Add all fieldnames again chklistColumns.Items.BeginUpdate; chklistColumns.Clear; - ds := Mainform.SelectedTableColumns; - ds.First; - while not ds.Eof do begin - chklistColumns.Items.Add(ds.Fields[0].AsWideString); - ds.Next; + Results := Mainform.SelectedTableColumns; + Results.First; + while not Results.Eof do begin + chklistColumns.Items.Add(Results.Col(0)); + Results.Next; end; chklistColumns.Items.EndUpdate; end; diff --git a/source/communication.pas b/source/communication.pas deleted file mode 100644 index 97b2224d..00000000 --- a/source/communication.pas +++ /dev/null @@ -1,514 +0,0 @@ -unit communication; - -(* - Functions for Inter-Process Communication. - - Note about WM_COPYDATA: - This message must be sent synchronously with SendMessage, - cannot be sent asynchronously using PostMessage. I think - the reason is related to the idea behind WM_COPYDATA causing - Windows to do a context switch to the receiving application.. -*) - - -interface - -uses - Db, - Windows, - Threading, - Classes, - Messages; - -const - // Our custom message types. - WM_COMPLETED = WM_APP + 1; - WM_PROCESSLOG = WM_APP + 2; - WM_MYSQL_THREAD_NOTIFY = WM_APP + 3; - WM_CLEAR_RIGHTCLICK_POINTER = WM_APP + 4; - WM_REFILL_SPAREBUF = WM_APP + 5; - // Our message subtypes for WM_COPYDATA messages. - CMD_EXECUTEQUERY_NORESULTS = 1; { Slightly faster - Fire-and-forget, no results } - CMD_EXECUTEQUERY_RESULTS = 2; { Normal - Wait for completion, fetch results } - RES_QUERYDATA = 257; - RES_EXCEPTION = 258; - RES_NONQUERY = 259; - // Our custom return codes. - ERR_NOERROR = 0; - ERR_UNSPECIFIED = 1; - -type - TNonQueryRunner = procedure(sendingApp: THandle; query: string) of object; - TQueryRunner = function(sendingApp: THandle; query: string): TDataSet of object; - - -(* - Run this procedure at application startup. -*) -procedure InitializeComm(myWindow: THandle; nonQueryRunner: TNonQueryRunner; queryRunner: TQueryRunner); - -(* - Execute a query on another window, request results but don't wait for them. -*) -function RemoteExecSqlAsync(handler: TCompletionHandler; timeout: Cardinal; window: THandle; query: String; method: DWORD; waitControl: TObject = nil): Cardinal; - -(* - Execute a query on another window, showing a wait dialog while processing - and calling a completion handler via messaging when done. -*) -function RemoteExecSql(handler: TCompletionHandler; timeout: Cardinal; window: THandle; query: String; info: String; method: DWORD): Cardinal; - -(* - Execute a query on another window, showing a wait dialog while processing - and returning results or raising an exception when done. -*) -function RemoteExecQuery(window: THandle; query: String; info: String): TDataSet; - -(* - Execute a query on a window. -*) -procedure RemoteExecNonQuery(window: THandle; query: string; info: string = ''); - -(* - Execute a USE query on a window, - given the version of the mysql server that window is connected to. -*) -procedure RemoteExecUseNonQuery(window: THandle; mysqlVersion: integer; dbName: string; info: string = ''); - -(* - Fill in resulting data and return waiting thread to caller. - Call from message handler when a query has finished executing and results are ready. -*) -procedure FinishRemoteExecution(msg: TWMCopyData); - -(* - Slightly lame wrapper: Call to release a RemoteXXX SendMessage call on the "other side". -*) -procedure ReleaseRemoteCaller(errCode: integer); - -(* - Extract a SQL query from a WM_COPYDATA message. -*) -function GetQueryFromMsg(msg: TWMCopyData): string; - -(* - Extract a request id from a WM_COPYDATA message. -*) -function GetRequestIdFromMsg(msg: TWMCopyData): Cardinal; - -(* - Helper which will handle WM_COMPLETED messages received. -*) -procedure HandleWMCompleteMessage(var msg: TMessage); - -(* - Helper which will handle WM_COPYDATA messages received. -*) -procedure HandleWMCopyDataMessage(var msg: TWMCopyData); - - -implementation - -uses - Forms, - Dialogs, - AdoDb, - AdoInt, - ActiveX, - Helpers, - SysUtils; - -type - TCopyDataStruct = packed record - dwData: LongWord; // up to 32 bits of data to be passed to the receiving application. - cbData: LongWord; // the size, in bytes, of the data pointed to by the lpData member. - lpData: Pointer; // points to data to be passed to the receiving application. This member can be nil. - end; - -var - sender: THandle; - nqRunner: TNonQueryRunner; - qRunner: TQueryRunner; - - -function CopyDataSetToAdoDataSet(src: TDataSet): TAdoDataSet; -var - dst: TAdoDataSet; - i: Integer; -begin - dst := TAdoDataSet.Create(nil); - dst.FieldDefs.Assign(src.FieldDefs); - dst.CreateDataSet; - src.First; - while not src.Eof do begin - dst.Append; - for i := 0 to dst.FieldCount - 1 do begin - dst.Fields[i].Assign(src.Fields[i]); - end; - dst.Post; - src.Next; - end; - result := dst; -end; - - -procedure SendDatasetToRemote(window: THandle; request: Cardinal; resType: integer; ds: TDataSet); -var - adods: TAdoDataSet; - data: TCopyDataStruct; - ms: TMemoryStream; - sa: TStreamAdapter; - olevar: OleVariant; -begin - ms := TMemoryStream.Create; - try - ms.Write(request, sizeof(Cardinal)); - if resType <> RES_NONQUERY then begin - adods := CopyDataSetToAdoDataSet(ds); - sa := TStreamAdapter.Create(ms); - olevar := adods.Recordset; - olevar.Save(sa as IStream, 0); - end; - debug(Format('ipc: Sending data set to window %d, request id %d, size %d', [window, request, ms.Size])); - data.dwData := resType; - data.cbData := ms.Size; - data.lpData := ms.Memory; - SendMessage(window, WM_COPYDATA, sender, integer(@data)); - finally - ms.free; - end; -end; - - -procedure SendErrorStringToRemote(window: THandle; request: Cardinal; resType: integer; error: string); -var - data: TCopyDataStruct; - ms: TMemoryStream; - pcerr: PChar; -begin - ms := TMemoryStream.Create; - try - ms.Write(request, sizeof(Cardinal)); - pcerr := PChar(error); - ms.Write(pcerr^, StrLen(pcerr) + 1); - debug(Format('ipc: Sending error message to window %d, request id %d, size %d', [window, request, ms.Size])); - data.dwData := resType; - data.cbData := ms.Size; - data.lpData := ms.Memory; - SendMessage(window, WM_COPYDATA, sender, integer(@data)); - finally - ms.free; - end; -end; - - -function GetStringFromMsgInternal(msg: TWMCopyData): string; -var - pcsql: PChar; - ms: TMemoryStream; -begin - ms := TMemoryStream.Create; - try - with msg.CopyDataStruct^ do begin - ms.Write(lpData^, cbData); - ms.Position := sizeof(Cardinal); - pcsql := StrAlloc(ms.Size - sizeof(Cardinal)); - ms.Read(pcsql^, ms.Size - sizeof(Cardinal)); - result := pcsql; - end; - finally - ms.free; - end; -end; - - -function GetQueryFromMsg(msg: TWMCopyData): string; -begin - result := GetStringFromMsgInternal(msg); -end; - - -function GetExceptionTextFromMsg(msg: TWMCopyData): string; -begin - result := GetStringFromMsgInternal(msg); -end; - - -function GetRequestIdFromMsg(msg: TWMCopyData): Cardinal; -var - req: Cardinal; - ms: TMemoryStream; -begin - ms := TMemoryStream.Create; - try - with msg.CopyDataStruct^ do begin - ms.Write(lpData^, cbData); - ms.Position := 0; - ms.Read(req, sizeof(Cardinal)); - result := req; - end; - finally - ms.free; - end; -end; - - -function GetDataSetFromMsg(msg: TWMCopyData): TDataSet; -var - adods: TAdoDataSet; - ms: TMemoryStream; - sa: TStreamAdapter; - olevar: OleVariant; -begin - ms := TMemoryStream.Create; - try - with msg.CopyDataStruct^ do begin - ms.Write(lpData^, cbData); - ms.Position := sizeof(Cardinal); - sa := TStreamAdapter.Create(ms); - olevar := CoRecordset.Create; - olevar.Open(sa as IStream); - adods := TAdoDataSet.Create(nil); - adods.Recordset := IUnknown(olevar) as _Recordset; - result := adods; - end; - finally - ms.free; - end; -end; - - -procedure RemoteExecSqlInternal(method: DWORD; req: Cardinal; window: THandle; query: String); -var - ms: TMemoryStream; - pcsql: PChar; - data: TCopyDataStruct; - err: integer; -begin - ms := TMemoryStream.Create; - try - debug(Format('ipc: Remote query being requested, id %d.', [req])); - ms.Write(req, sizeof(Cardinal)); - pcsql := PChar(query); - ms.Write(pcsql^, StrLen(pcsql) + 1); - data.dwData := method; - data.cbData := ms.Size; - data.lpData := ms.Memory; - err := SendMessage(window, WM_COPYDATA, sender, integer(@data)); - if err <> 0 then Exception.CreateFmt('Remote returned error %d when asked to execute query', [err]); - finally - ms.free; - end; -end; - - -function RemoteExecSqlAsync(handler: TCompletionHandler; timeout: Cardinal; window: THandle; query: String; method: DWORD; waitControl: TObject = nil): Cardinal; -var - req: Cardinal; -begin - req := SetCompletionHandler(handler, timeout, waitControl); - RemoteExecSqlInternal(method, req, window, query); - result := req; -end; - - -function RemoteExecSql(handler: TCompletionHandler; timeout: Cardinal; window: THandle; query: String; info: String; method: DWORD): Cardinal; -var - cancelDialog: TForm; - requestId: Cardinal; -begin - if Length(info) = 0 then info := 'Waiting for remote session to execute query...'; - cancelDialog := CreateMessageDialog(info, mtCustom, [mbCancel]); - requestId := RemoteExecSqlAsync(handler, timeout, window, query, method, cancelDialog); - // The callback method shouldn't be activated before messages has been processed, - // so we can safely touch the wait control (a cancel dialog) here. - cancelDialog.ShowModal; - // We just cancel in any case. - // If the query was completed before the cancel dialog closed, - // the notification code won't accept the cancel, so it's OK. - NotifyInterrupted(requestId, Exception.Create('User cancelled.')); - result := RequestId; -end; - - -function RemoteExecQuery(window: THandle; query: String; info: String): TDataSet; -var - requestId: Cardinal; -begin - // Call with no handler (= no completion message) and no timeout. - requestId := RemoteExecSql(nil, INFINITE_TIMEOUT, window, query, info, CMD_EXECUTEQUERY_RESULTS); - // Take care of results since there's no handler. - result := TDataSet(ExtractResultObject(requestId)); -end; - - -procedure RemoteExecNonQuery(window: THandle; query: string; info: string); -var - requestId: Cardinal; -begin - // Call with no handler (= no completion message) and no timeout. - requestId := RemoteExecSql(nil, INFINITE_TIMEOUT, window, query, info, CMD_EXECUTEQUERY_NORESULTS); - // Take care of results since there's no handler. - ExtractResultObject(requestId); -end; - - -{*** - Note: Broken, will not work as intended. - Remote window needs to set TemporaryDatabase (and reset it afterwards) - for this to work. Also, queries are fired asynchronously, so the user - may change the active database at any point. It is safest to just add - the database name explicitly in the SQL rather than to run USE remotely. -} -procedure RemoteExecUseNonQuery(window: THandle; mysqlVersion: integer; dbName: string; info: string); -begin - RemoteExecNonQuery(window, 'USE ' + maskSql(mysqlVersion, dbName), info); -end; - - -procedure SwitchWaitControlInternal(waitControl: TObject); -var - cancelDialog: TForm; -begin - // Hide the cancel dialog if it's still showing. - cancelDialog := TForm(waitControl); - if (cancelDialog <> nil) and cancelDialog.Visible then cancelDialog.Close; -end; - - -procedure FinishRemoteExecution(msg: TWMCopyData); -var - res: TDataSet; - req: Cardinal; - s: string; -begin - req := GetRequestIdFromMsg(msg); - debug(Format('ipc: Remote execute query call finished for request id %d.', [req])); - case msg.CopyDataStruct^.dwData of - RES_QUERYDATA: begin - res := GetDataSetFromMsg(msg); - NotifyComplete(req, res); - end; - RES_EXCEPTION: begin - s := GetExceptionTextFromMsg(msg); - NotifyFailed(req, Exception.Create('Error from remote: ' + s)); - end; - RES_NONQUERY: begin - // Uses a blank object to indicate completed queries with no result data.. - NotifyComplete(req, TObject.Create()); - end; - end; -end; - - -procedure ReleaseRemoteCaller(errCode: integer); -begin - // reply to the message so the clients thread is unblocked - ReplyMessage(errCode); -end; - - -procedure ReportFinishedQuery(method: DWORD; window: THandle; request: Cardinal; ds: TDataSet); -var - resType: DWORD; -begin - if method = CMD_EXECUTEQUERY_NORESULTS then resType := RES_NONQUERY - else resType := RES_QUERYDATA; - SendDataSetToRemote(window, request, resType, ds); -end; - - -procedure ReportFailedQuery(method: DWORD; window: THandle; request: Cardinal; error: string); overload; -begin - SendErrorStringToRemote(window, request, RES_EXCEPTION, error); -end; - - -procedure HandleWMCompleteMessage(var msg: TMessage); -var - req: Cardinal; - res: TNotifyStructure; - callback: TCompletionHandler; -begin - debug('ipc: Handling WM_COMPLETED.'); - try - // Extract results. - req := msg.LParam; - res := ExtractResults(req, true); - // Switch wait control to non-waiting state. - SwitchWaitControlInternal(res.GetWaitControl); - // Perform rest of completion via callback, if any. - callback := res.GetHandler; - if @callback <> nil then begin - // Clear results. - ExtractResults(req); - // Perform callback. - callback(res); - end; - // Otherwise just assume that completion will be handled - // by some thread which were waiting for the wait control. - // - // In the future, we could explicitly sound an event for - // this purpose, in case it's not possible to wait on the - // wait control.. - finally - ReleaseRemoteCaller(ERR_NOERROR); - end; -end; - - -procedure HandleWMCopyDataMessage(var msg: TWMCopyData); -var - method: DWORD; - query: string; - remoteReqId: integer; - data: TDataSet; - //tab: THandle; -begin - debug('ipc: Handling WM_COPYDATA.'); - method := msg.CopyDataStruct^.dwData; - if - (method = CMD_EXECUTEQUERY_NORESULTS) or - (method = CMD_EXECUTEQUERY_RESULTS) - then begin - try - remoteReqId := GetRequestIdFromMsg(msg); - query := GetQueryFromMsg(msg); - finally - ReleaseRemoteCaller(ERR_NOERROR); - end; - try - if method = CMD_EXECUTEQUERY_NORESULTS then begin - nqRunner(msg.From, query); - ReportFinishedQuery(method, msg.From, remoteReqId, nil); - end else begin - data := qRunner(msg.From, query); - ReportFinishedQuery(method, msg.From, remoteReqId, data); - end; - except - on e: Exception do begin - ReportFailedQuery(method, msg.From, remoteReqId, e.Message); - end; - end; - end; - if - (method = RES_QUERYDATA) or - (method = RES_EXCEPTION) or - (method = RES_NONQUERY) - then begin - ReleaseRemoteCaller(ERR_NOERROR); - FinishRemoteExecution(msg); - end; -end; - - -procedure InitializeComm(myWindow: THandle; nonQueryRunner: TNonQueryRunner; queryRunner: TQueryRunner); -begin - sender := myWindow; - nqRunner := nonQueryRunner; - qRunner := queryRunner; -end; - - -end. - diff --git a/source/copytable.pas b/source/copytable.pas index b88fdf2e..5975186a 100644 --- a/source/copytable.pas +++ b/source/copytable.pas @@ -10,8 +10,8 @@ interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, - StdCtrls, Buttons, CheckLst, ZDataSet, ComCtrls, WideStrings, - TntStdCtrls, TntCheckLst; + StdCtrls, Buttons, CheckLst, ComCtrls, WideStrings, + TntStdCtrls, TntCheckLst, mysql_connection; type TCopyTableForm = class(TForm) @@ -111,7 +111,6 @@ procedure TCopyTableForm.FormShow(Sender: TObject); var i : Integer; struc_data : Byte; - ds: TDataSet; NodeData: PVTreeData; begin if Mainform.DBtree.Focused then @@ -132,15 +131,7 @@ begin comboSelectDatabase.ItemIndex := 0; // fill columns: - CheckListBoxFields.Items.Clear; - ds := Mainform.GetResults( 'SHOW FIELDS FROM ' + mainform.mask(oldTableName) ); - for i:=1 to ds.RecordCount do - begin - CheckListBoxFields.Items.Add( ds.Fields[0].AsWideString ); - ds.Next; - end; - ds.Close; - FreeAndNil(ds); + CheckListBoxFields.Items.Text := Mainform.Connection.GetCol('SHOW FIELDS FROM ' + mainform.mask(oldTableName)).Text; // select all: for i:=0 to CheckListBoxFields.Items.Count-1 do @@ -174,7 +165,7 @@ var keystr : WideString; notnull, default : WideString; - zq : TDataSet; + Results : TMySQLQuery; isFulltext : Boolean; struc_data : Byte; Fixes : TWideStringlist; @@ -194,17 +185,17 @@ begin // keys > if CheckBoxWithIndexes.Checked then begin - zq := Mainform.GetResults( 'SHOW KEYS FROM ' + mainform.mask(oldtablename) ); + Results := Mainform.Connection.GetResults('SHOW KEYS FROM ' + mainform.mask(oldtablename)); setLength(keylist, 0); keystr := ''; - for i:=1 to zq.RecordCount do + for i:=1 to Results.RecordCount do begin which := -1; for k:=0 to length(keylist)-1 do begin - if keylist[k].Name = zq.Fields[2].AsString then // keyname exists! + if keylist[k].Name = Results.Col(2) then // keyname exists! which := k; end; if which = -1 then @@ -213,30 +204,27 @@ begin which := high(keylist); keylist[which].Columns := TWideStringList.Create; keylist[which].SubParts := TWideStringList.Create; - with keylist[which] do // set properties for new key - begin - if Mainform.mysql_version < 40002 then - isFulltext := (zq.FieldByName('Comment').AsString = 'FULLTEXT') - else - isFulltext := (zq.FieldByName('Index_type').AsString = 'FULLTEXT'); - Name := zq.Fields[2].AsString; - if zq.Fields[2].AsString = 'PRIMARY' then - _type := 'PRIMARY' - else if isFulltext then - _type := 'FULLTEXT' - else if zq.Fields[1].AsString = '1' then - _type := '' - else if zq.Fields[1].AsString = '0' then - _type := 'UNIQUE'; - end; + // set properties for new key + if Mainform.Connection.ServerVersionInt < 40002 then + isFulltext := Results.Col('Comment') = 'FULLTEXT' + else + isFulltext := Results.Col('Index_type') = 'FULLTEXT'; + keylist[which].Name := Results.Col(2); + if Results.Col(2) = 'PRIMARY' then + keylist[which]._type := 'PRIMARY' + else if isFulltext then + keylist[which]._type := 'FULLTEXT' + else if Results.Col(1) = '1' then + keylist[which]._type := '' + else if Results.Col(1) = '0' then + keylist[which]._type := 'UNIQUE'; end; // add column - keylist[which].Columns.add( zq.FieldByName('Column_Name').AsWideString ); - keylist[which].SubParts.add( zq.FieldByName('Sub_part').AsWideString ); - zq.Next; + keylist[which].Columns.add(Results.Col('Column_Name')); + keylist[which].SubParts.add(Results.Col('Sub_part')); + Results.Next; end; - zq.Close; - FreeAndNil(zq); + FreeAndNil(Results); for k:=0 to high(keylist) do begin if k > 0 then @@ -261,16 +249,16 @@ begin // < keys // Add collation and engine clauses - zq := Mainform.FetchActiveDbTableList; - while not zq.Eof do begin - if zq.FieldByName(DBO_NAME).AsWideString = oldTableName then begin - if (zq.FindField(DBO_COLLATION) <> nil) and (zq.FieldByName(DBO_COLLATION).AsString <> '') then - strquery := strquery + ' COLLATE ' + zq.FieldByName(DBO_COLLATION).AsString; - if (zq.FindField(DBO_ENGINE) <> nil) and (zq.FieldByName(DBO_ENGINE).AsString <> '') then - strquery := strquery + ' ENGINE=' + zq.FieldByName(DBO_ENGINE).AsString; + Results := Mainform.FetchActiveDbTableList; + while not Results.Eof do begin + if Results.Col(DBO_NAME) = oldTableName then begin + if Results.ColExists(DBO_COLLATION) and (Results.Col(DBO_COLLATION) <> '') then + strquery := strquery + ' COLLATE ' + Results.Col(DBO_COLLATION); + if Results.ColExists(DBO_ENGINE) and (Results.Col(DBO_ENGINE) <> '') then + strquery := strquery + ' ENGINE=' + Results.Col(DBO_ENGINE); break; end; - zq.Next; + Results.Next; end; strquery := strquery + ' SELECT'; @@ -291,40 +279,39 @@ begin if radioStructure.Checked then strquery := strquery + ' WHERE 1 = 0'; - Mainform.ExecUpdateQuery(strquery); + Mainform.Connection.Query(strquery, False); // Fix missing auto_increment property and CURRENT_TIMESTAMP defaults in new table - zq := Mainform.GetResults('SHOW FIELDS FROM ' + mainform.mask(oldtablename)); + Results := Mainform.Connection.GetResults('SHOW FIELDS FROM ' + mainform.mask(oldtablename)); Fixes := TWideStringlist.Create; - while not zq.Eof do begin + while not Results.Eof do begin notnull := ''; - if zq.FieldByName('Null').AsString = '' then + if Results.Col('Null') = '' then notnull := 'NOT NULL'; default := ''; - if zq.FieldByName('Default').AsWideString <> '' then begin + if Results.Col('Default') <> '' then begin default := 'DEFAULT '; - if zq.FieldByName('Default').AsWideString = 'CURRENT_TIMESTAMP' then - default := default + zq.FieldByName('Default').AsWideString + if Results.Col('Default') = 'CURRENT_TIMESTAMP' then + default := default + Results.Col('Default') else - default := default + esc(zq.FieldByName('Default').AsWideString); + default := default + esc(Results.Col('Default')); end; - if (CheckBoxWithIndexes.Checked and (zq.FieldByName('Extra').AsString = 'auto_increment')) - or (zq.FieldByName('Default').AsString = 'CURRENT_TIMESTAMP') then begin - Fixes.Add('CHANGE '+Mainform.mask(zq.FieldByName('Field').AsWideString)+' '+ - Mainform.mask(zq.FieldByName('Field').AsWideString)+' '+ - zq.FieldByName('Type').AsWideString+' '+default+' '+notnull+' '+zq.FieldByName('Extra').AsString); + if (CheckBoxWithIndexes.Checked and (Results.Col('Extra') = 'auto_increment')) + or (Results.Col('Default') = 'CURRENT_TIMESTAMP') then begin + Fixes.Add('CHANGE '+Mainform.mask(Results.Col('Field'))+' '+ + Mainform.mask(Results.Col('Field'))+' '+ + Results.Col('Type')+' '+default+' '+notnull+' '+Results.Col('Extra')); end; - zq.Next; + Results.Next; end; if Fixes.Count > 0 then begin - Mainform.ExecUpdateQuery('ALTER TABLE '+Mainform.mask(ComboSelectDatabase.Text) + '.'+Mainform.mask(editNewTablename.Text)+ ' '+ + Mainform.Connection.Query('ALTER TABLE '+Mainform.mask(ComboSelectDatabase.Text) + '.'+Mainform.mask(editNewTablename.Text)+ ' '+ ImplodeStr(', ', Fixes) ); end; - zq.Close; - FreeAndNil(zq); + Results.Free; FreeAndNil(Fixes); Mainform.actRefresh.Execute; diff --git a/source/createdatabase.pas b/source/createdatabase.pas index 98d11e21..7c1f0c62 100644 --- a/source/createdatabase.pas +++ b/source/createdatabase.pas @@ -4,7 +4,7 @@ interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, - Dialogs, StdCtrls, db, SynEdit, SynMemo, TntStdCtrls, WideStrings; + Dialogs, StdCtrls, mysql_connection, SynEdit, SynMemo, TntStdCtrls, WideStrings; type TCreateDatabaseForm = class(TForm) @@ -29,7 +29,7 @@ type function GetCreateStatement: WideString; private { Private declarations } - dsCollations : TDataSet; + dsCollations : TMySQLQuery; defaultCharset : String; currentCollation : String; public @@ -55,9 +55,9 @@ begin InheritFont(Font); try - dsCollations := Mainform.GetResults('SHOW COLLATION'); + dsCollations := Mainform.Connection.GetResults('SHOW COLLATION'); // Detect servers default charset - defaultCharset := Mainform.GetVar( 'SHOW VARIABLES LIKE '+esc('character_set_server'), 1 ); + defaultCharset := Mainform.Connection.GetVar( 'SHOW VARIABLES LIKE '+esc('character_set_server'), 1 ); except // Ignore it when the above statements don't work on pre 4.1 servers. // If the list(s) are nil, disable the combobox(es), so we create the db without charset. @@ -70,9 +70,8 @@ begin begin comboCharset.Items.BeginUpdate; dsCollations.First; - while not dsCollations.Eof do - begin - charset := dsCollations.FieldByName('Charset').AsString; + while not dsCollations.Eof do begin + charset := dsCollations.Col('Charset'); if comboCharset.Items.IndexOf(charset) = -1 then comboCharset.Items.Add(charset); dsCollations.Next; @@ -90,7 +89,6 @@ end; procedure TCreateDatabaseForm.FormDestroy(Sender: TObject); begin - if dsCollations <> nil then dsCollations.Close; FreeAndNil(dsCollations); end; @@ -118,7 +116,7 @@ begin editDBName.SelectAll; // Detect current charset and collation to be able to preselect them in the pulldowns - sql_create := Mainform.GetVar('SHOW CREATE DATABASE '+Mainform.mask(modifyDB), 1); + sql_create := Mainform.Connection.GetVar('SHOW CREATE DATABASE '+Mainform.mask(modifyDB), 1); currentCharset := Copy( sql_create, pos('CHARACTER SET', sql_create)+14, Length(sql_create)); currentCharset := GetFirstWord( currentCharset ); if currentCharset <> '' then @@ -166,13 +164,12 @@ begin comboCollation.Items.BeginUpdate; comboCollation.Items.Clear; dsCollations.First; - while not dsCollations.Eof do - begin - if dsCollations.FieldByName('Charset').AsString = comboCharset.Text then + while not dsCollations.Eof do begin + if dsCollations.Col('Charset') = comboCharset.Text then begin - comboCollation.Items.Add( dsCollations.FieldByName('Collation').AsString ); - if dsCollations.FieldByName('Default').AsString = 'Yes' then - defaultCollation := dsCollations.FieldByName('Collation').AsString; + comboCollation.Items.Add( dsCollations.Col('Collation')); + if dsCollations.Col('Default') = 'Yes' then + defaultCollation := dsCollations.Col('Collation'); end; dsCollations.Next; end; @@ -224,12 +221,12 @@ procedure TCreateDatabaseForm.btnOKClick(Sender: TObject); var sql : WideString; AllDatabases, Unions, ObjectsLeft: TWideStringList; - ObjectsInNewDb, ObjectsInOldDb: TDataset; + ObjectsInNewDb, ObjectsInOldDb: TMySQLQuery; OldObjType, NewObjType: TListNodeType; begin if modifyDB = '' then try sql := GetCreateStatement; - Mainform.ExecUpdateQuery( sql ); + Mainform.Connection.Query(sql); // Close form ModalResult := mrOK; except @@ -246,25 +243,25 @@ begin end; if modifyDB = editDBName.Text then begin // Alter database - Mainform.ExecUpdateQuery(sql); + Mainform.Connection.Query(sql); end else begin // Rename database ObjectsInOldDb := MainForm.RefreshDbTableList(modifyDB); - AllDatabases := Mainform.GetCol('SHOW DATABASES'); + AllDatabases := Mainform.Connection.GetCol('SHOW DATABASES'); if AllDatabases.IndexOf(editDBName.Text) = -1 then begin // Target db does not exist - create it - Mainform.ExecUpdateQuery(GetCreateStatement); + Mainform.Connection.Query(GetCreateStatement); end else begin // Target db exists - warn if there are tables with same names ObjectsInNewDb := MainForm.RefreshDbTableList(editDBName.Text); while not ObjectsInNewDb.Eof do begin - NewObjType := GetDBObjectType(ObjectsInNewDb.Fields); + NewObjType := GetDBObjectType(ObjectsInNewDb); ObjectsInOldDb.First; while not ObjectsInOldDb.Eof do begin - OldObjType := GetDBObjectType(ObjectsInOldDb.Fields); + OldObjType := GetDBObjectType(ObjectsInOldDb); if not (OldObjType in [lntTable, lntCrashedTable, lntView]) then Raise Exception.Create('Database "'+modifyDB+'" contains stored routine(s), which cannot be moved.'); - if (ObjectsInOldDb.FieldByName(DBO_NAME).AsWideString = ObjectsInNewDb.FieldByName(DBO_NAME).AsWideString) + if (ObjectsInOldDb.Col(DBO_NAME) = ObjectsInNewDb.Col(DBO_NAME)) and (OldObjType = NewObjType) then begin // One or more objects have a naming conflict Raise Exception.Create('Database "'+editDBName.Text+'" exists and has objects with same names as in "'+modifyDB+'"'); @@ -282,12 +279,12 @@ begin ObjectsInOldDb.First; sql := 'RENAME TABLE '; while not ObjectsInOldDb.Eof do begin - sql := sql + Mainform.mask(modifyDb)+'.'+Mainform.mask(ObjectsInOldDb.FieldByName(DBO_NAME).AsWideString)+' TO '+ - Mainform.mask(editDBName.Text)+'.'+Mainform.mask(ObjectsInOldDb.FieldByName(DBO_NAME).AsWideString)+', '; + sql := sql + Mainform.mask(modifyDb)+'.'+Mainform.mask(ObjectsInOldDb.Col(DBO_NAME))+' TO '+ + Mainform.mask(editDBName.Text)+'.'+Mainform.mask(ObjectsInOldDb.Col(DBO_NAME))+', '; ObjectsInOldDb.Next; end; Delete(sql, Length(sql)-1, 2); - Mainform.ExecUpdateQuery(sql); + Mainform.Connection.Query(sql); Mainform.ClearDbTableList(modifyDB); Mainform.ClearDbTableList(editDBName.Text); // Last step for renaming: drop source database @@ -302,12 +299,12 @@ begin if Mainform.InformationSchemaTables.IndexOf('TRIGGERS') > -1 then Unions.Add('SELECT 1 FROM '+Mainform.mask(DBNAME_INFORMATION_SCHEMA)+'.TRIGGERS WHERE TRIGGER_SCHEMA='+esc(modifyDB)); if Unions.Count = 1 then - ObjectsLeft := Mainform.GetCol(Unions[0]) + ObjectsLeft := Mainform.Connection.GetCol(Unions[0]) else if Unions.Count > 1 then - ObjectsLeft := Mainform.GetCol('(' + implodestr(') UNION (', Unions) + ')'); + ObjectsLeft := Mainform.Connection.GetCol('(' + implodestr(') UNION (', Unions) + ')'); end; if ObjectsLeft.Count = 0 then begin - Mainform.ExecUpdateQuery('DROP DATABASE '+modifyDB); + Mainform.Connection.Query('DROP DATABASE '+modifyDB); end; FreeAndNil(ObjectsLeft); end; diff --git a/source/data_sorting.pas b/source/data_sorting.pas index f7265422..2bc411d4 100644 --- a/source/data_sorting.pas +++ b/source/data_sorting.pas @@ -5,7 +5,7 @@ interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, Buttons, - WideStrings, TntStdCtrls, helpers, Db; + WideStrings, TntStdCtrls, helpers, mysql_connection; type @@ -60,15 +60,15 @@ end; } procedure TDataSortingForm.FormShow(Sender: TObject); var - ds: TDataset; + Results: TMySQLQuery; begin // Take column names from listColumns and add here ColumnNames.Clear; - ds := Mainform.SelectedTableColumns; - ds.First; - while not ds.Eof do begin - ColumnNames.Add(ds.Fields[0].AsWideString); - ds.Next; + Results := Mainform.SelectedTableColumns; + Results.First; + while not Results.Eof do begin + ColumnNames.Add(Results.Col(0)); + Results.Next; end; OrderColumns := Mainform.FDataGridSort; diff --git a/source/dataviewsave.pas b/source/dataviewsave.pas index 16204692..af7b0675 100644 --- a/source/dataviewsave.pas +++ b/source/dataviewsave.pas @@ -67,7 +67,7 @@ begin HiddenCols.StrictDelimiter := True; Mainform.SelectedTableColumns.First; for i := 0 to Mainform.SelectedTableColumns.RecordCount - 1 do begin - Col := Mainform.SelectedTableColumns.Fields[0].AsWideString; + Col := Mainform.SelectedTableColumns.Col(0); if Mainform.FDataGridSelect.IndexOf(Col) = -1 then HiddenCols.Add(Col); Mainform.SelectedTableColumns.Next; diff --git a/source/editvar.pas b/source/editvar.pas index b030c5bd..327cd3fe 100644 --- a/source/editvar.pas +++ b/source/editvar.pas @@ -70,9 +70,10 @@ begin // Set the value and keep the form open in any error case try - Mainform.ExecUpdateQuery(sql, False, True); + Mainform.Connection.Query(sql); except ModalResult := mrNone; + Raise; end; end; diff --git a/source/exportsql.dfm b/source/exportsql.dfm index e8fdf0b2..b6e0e516 100644 --- a/source/exportsql.dfm +++ b/source/exportsql.dfm @@ -148,13 +148,13 @@ object ExportSQLForm: TExportSQLForm Left = 235 Top = 0 Width = 376 - Height = 200 + Height = 161 Anchors = [akLeft, akTop, akRight] Caption = 'Output' TabOrder = 0 DesignSize = ( 376 - 200) + 161) object btnFileBrowse: TPngSpeedButton Left = 344 Top = 42 @@ -218,40 +218,7 @@ object ExportSQLForm: TExportSQLForm Color = clBtnFace Enabled = False ItemHeight = 13 - TabOrder = 6 - end - object radioOtherHost: TRadioButton - Left = 9 - Top = 152 - Width = 256 - Height = 17 - Caption = 'Another host and optionally another database' - TabOrder = 8 - OnClick = radioOtherHostClick - end - object comboOtherHost: TComboBox - Left = 26 - Top = 169 - Width = 137 - Height = 21 - Style = csDropDownList - Color = clBtnFace - Enabled = False - ItemHeight = 13 TabOrder = 5 - OnSelect = comboOtherHostSelect - end - object comboOtherHostDatabase: TTntComboBox - Left = 168 - Top = 169 - Width = 198 - Height = 21 - Style = csDropDownList - Anchors = [akLeft, akTop, akRight] - Color = clBtnFace - Enabled = False - ItemHeight = 13 - TabOrder = 7 end object radioDirectory: TRadioButton Left = 9 @@ -278,9 +245,9 @@ object ExportSQLForm: TExportSQLForm end object groupExampleSql: TGroupBox Left = 235 - Top = 206 + Top = 167 Width = 376 - Height = 107 + Height = 146 Anchors = [akLeft, akTop, akRight, akBottom] Caption = 'Example SQL' TabOrder = 1 @@ -288,7 +255,7 @@ object ExportSQLForm: TExportSQLForm Left = 2 Top = 15 Width = 372 - Height = 90 + Height = 129 SingleLineMode = False Align = alClient Color = clBtnFace diff --git a/source/exportsql.pas b/source/exportsql.pas index 199e232b..00123031 100644 --- a/source/exportsql.pas +++ b/source/exportsql.pas @@ -9,7 +9,6 @@ unit exportsql; interface uses - Threading, Windows, Messages, SysUtils, @@ -24,11 +23,9 @@ uses Buttons, comctrls, ToolWin, - DB, SynEdit, SynMemo, - ZDataSet, - PngSpeedButton, StdActns, WideStrings, TntCheckLst, TntStdCtrls, Menus; + PngSpeedButton, StdActns, WideStrings, TntCheckLst, TntStdCtrls, Menus, mysql_connection, mysql_structures; type TExportSQLForm = class(TForm) @@ -52,9 +49,6 @@ type radioOtherDatabase: TRadioButton; radioFile: TRadioButton; comboOtherDatabase: TTNTComboBox; - radioOtherHost: TRadioButton; - comboOtherHost: TComboBox; - comboOtherHostDatabase: TTNTComboBox; groupExampleSql: TGroupBox; SynMemoExampleSQL: TSynMemo; groupOptions: TGroupBox; @@ -72,7 +66,6 @@ type btnDirectoryBrowse: TPngSpeedButton; procedure FormCreate(Sender: TObject); procedure comboTargetCompatChange(Sender: TObject); - procedure comboOtherHostSelect(Sender: TObject); procedure comboDataChange(Sender: TObject); procedure comboTablesChange(Sender: TObject); procedure comboDatabaseChange(Sender: TObject); @@ -91,7 +84,6 @@ type procedure validateRadioControls(Sender: TObject); procedure validateControls(Sender: TObject); procedure cbxStructureClick(Sender: TObject); - procedure radioOtherHostClick(Sender: TObject); procedure cbxDataClick(Sender: TObject); procedure checkListTablesKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); @@ -113,9 +105,7 @@ implementation uses Main, - Helpers, - Synchronization, - Communication; + Helpers; {$R *.DFM} @@ -136,7 +126,6 @@ const // Order of radiobutton group "Output" OUTPUT_FILE = 1; OUTPUT_DB = 2; - OUTPUT_HOST = 3; OUTPUT_DIR = 4; // Default output compatibility @@ -148,8 +137,6 @@ const var appHandles: array of THandle; cancelDialog: TForm = nil; - remote_version: integer; - remote_max_allowed_packet : Int64; target_versions : TStringList; @@ -167,7 +154,6 @@ end; procedure TExportSQLForm.FormShow(Sender: TObject); var i, OutputTo : Integer; - list: TWindowDataArray; begin barProgress.Position := 0; lblProgress.Caption := ''; @@ -189,7 +175,7 @@ begin with target_versions do begin Add( IntToStr( SQL_VERSION_ANSI ) + '=ANSI SQL' ); - Add( IntToStr( Mainform.mysql_version ) + '=Same as source (' + ConvertServerVersion(Mainform.mysql_version) + ')'); + Add( IntToStr( Mainform.Connection.ServerVersionInt ) + '=Same as source (' + Mainform.Connection.ServerVersionStr + ')'); Add( '50100=HeidiSQL w/ MySQL Server 5.1' ); Add( '50000=HeidiSQL w/ MySQL Server 5.0' ); Add( '40100=HeidiSQL w/ MySQL Server 4.1' ); @@ -227,35 +213,11 @@ begin FilenameEdited := False; comboSelectDatabaseChange(self); editDirectory.Text := GetRegValue(REGNAME_EXP_OUTDIR, ''); - OutputTo := GetRegValue(REGNAME_EXP_TARGET, -1); - if OutputTo > -1 then - begin - - {*** - @note ansgarbecker, 2007-02-24 - If OutputTo is now OUTPUT_HOST and there are no other windows - to export to, reset OutputTo to OUTPUT_FILE to avoid the error-popup - "You need at least 2 windows...", which should not be fired in - FormShow rather than only when the user has really clicked that - radiobutton. - As a benefit, the OUTPUT_FILE won't be saved to registry here - so the OUTPUT_HOST is still enabled in registry and will be used - the next time if we have more than 1 window - @see bug #1666054 - } - // Check if all the heidisql windows are still alive. - CheckForCrashedWindows; - // Fetch list of heidisql windows. - list := GetWindowList; - if (Length(list) < 2) and (OutputTo = OUTPUT_HOST) then - OutputTo := OUTPUT_FILE; - - case OutputTo of - OUTPUT_FILE : radioFile.Checked := true; - OUTPUT_DIR : radioDirectory.Checked := true; - OUTPUT_DB : radioOtherDatabase.Checked := true; - OUTPUT_HOST : radioOtherHost.Checked := true; - end; + OutputTo := GetRegValue(REGNAME_EXP_TARGET, OUTPUT_FILE); + case OutputTo of + OUTPUT_FILE : radioFile.Checked := true; + OUTPUT_DIR : radioDirectory.Checked := true; + OUTPUT_DB : radioOtherDatabase.Checked := true; end; Width := GetRegValue(REGNAME_EXP_WINWIDTH, Width); Height := GetRegValue(REGNAME_EXP_WINHEIGHT, Height); @@ -281,77 +243,20 @@ begin generateExampleSQL; end; -procedure TExportSQLForm.comboOtherHostSelect(Sender: TObject); -var - data: TDataSet; - j: integer; - versions : TWideStringList; -begin - // Get both databases and version right when the radio - // is clicked, so we can switch to the 'file' radio - // immediately when something goes wrong. - try - data := RemoteExecQuery( - appHandles[comboOtherHost.ItemIndex], - 'SHOW DATABASES', - 'Fetching remote list of databases...' - ); - comboOtherHostDatabase.Clear; - for j:=0 to data.RecordCount - 1 do begin - comboOtherHostDatabase.Items.Add(data.FieldByName('Database').AsWideString); - data.Next; - end; - data.Free; - - data := RemoteExecQuery( - appHandles[comboOtherHost.ItemIndex], - 'SELECT VERSION()', - 'Probing for remote version...' - ); - versions := explode('.', data.Fields[0].AsWideString); - remote_version := MakeInt(versions[0]) * 10000 + MakeInt(versions[1]) * 100 + MakeInt(versions[2]); - data.Free; - - // Fetch the max_allowed_packet variable to be sure not to - // overload the server when using "Extended Insert" - data := RemoteExecQuery( - appHandles[comboOtherHost.ItemIndex], - 'SHOW VARIABLES LIKE ' + esc('max_allowed_packet'), - 'Checking for maximum allowed SQL-packet size on server '+comboOtherHost.Text+'...' - ); - remote_max_allowed_packet := MakeInt( data.FieldByName('Value').AsString ); - data.Free; - except - on E: Exception do begin - ShowMessage(E.Message); - radioFile.Checked := true; - E.Free; - end; - end; - - // Select remote database with the same name as the source db if available - if comboOtherHostDatabase.Items.IndexOf( comboSelectDatabase.Text ) > -1 then - comboOtherHostDatabase.ItemIndex := comboOtherHostDatabase.Items.IndexOf( comboSelectDatabase.Text ) - // Otherwise, select first database if available - else if comboOtherHostDatabase.Items.Count > 0 then - comboOtherHostDatabase.ItemIndex := 0; - -end; - procedure TExportSQLForm.comboSelectDatabaseChange(Sender: TObject); var i : Integer; CheckThisItem: Boolean; - ds: TDataset; + Results: TMySQLQuery; dir: WideString; begin // read tables from db checkListTables.Items.Clear; - ds := Mainform.FetchDbTableList(comboSelectDatabase.Text); - while not ds.Eof do begin - if GetDBObjectType(ds.Fields) = lntTable then - checkListTables.Items.Add(ds.FieldByName(DBO_NAME).AsWideString); - ds.Next; + Results := Mainform.FetchDbTableList(comboSelectDatabase.Text); + while not Results.Eof do begin + if GetDBObjectType(Results) = lntTable then + checkListTables.Items.Add(Results.Col(DBO_NAME)); + Results.Next; end; // select all/some: @@ -482,11 +387,8 @@ var keystr : WideString; sourceDb, destDb : WideString; which : Integer; - tofile,todb,tohost : boolean; - samehost : boolean; - sameuuid : TGuid; + tofile,todb : boolean; tcount,tablecounter : Integer; - win2export : THandle; StrProgress : String; value : WideString; Escaped,fullvalue : PChar; @@ -504,13 +406,13 @@ var RecordCount_all, RecordCount_one, RecordNo_all, offset, limit : Int64; sql_select : WideString; - query : TDataSet; + query : TMySQLQuery; OldActualDatabase : WideString; function sourceMask(sql: WideString): WideString; begin // Same as mask(sql). - Result := maskSql(Mainform.mysql_version, sql); + Result := maskSql(Mainform.Connection.ServerVersionInt, sql); end; function destMask(sql: WideString): WideString; @@ -540,7 +442,6 @@ begin // to where? tofile := radioFile.Checked or radioDirectory.Checked; todb := radioOtherDatabase.Checked; - tohost := radioOtherHost.Checked; // export! pageControl1.ActivePageIndex := 0; @@ -549,7 +450,6 @@ begin // Initialize default-variables target_version := SQL_VERSION_DEFAULT; target_cliwa := false; - win2export := 0; max_allowed_packet := 1024*1024; // export what? @@ -571,7 +471,7 @@ begin // Extract name part of selected target version target_version := MakeInt(target_versions.Names[comboTargetCompat.ItemIndex]); target_cliwa := Pos('mysqldump', target_versions.Names[comboTargetCompat.ItemIndex]) > 0; - max_allowed_packet := MakeInt( Mainform.GetVar( 'SHOW VARIABLES LIKE ' + esc('max_allowed_packet'), 1 ) ); + max_allowed_packet := MakeInt( Mainform.Connection.GetVar( 'SHOW VARIABLES LIKE ' + esc('max_allowed_packet'), 1 ) ); f := InitFileStream('header'); if f = nil then begin @@ -586,27 +486,12 @@ begin // Export to other database in the same window if todb then begin - target_version := Mainform.mysql_version; - max_allowed_packet := MakeInt( Mainform.GetVar( 'SHOW VARIABLES LIKE ' + esc('max_allowed_packet'), 1 ) ); + target_version := Mainform.Connection.ServerVersionInt; + max_allowed_packet := MakeInt( Mainform.Connection.GetVar( 'SHOW VARIABLES LIKE ' + esc('max_allowed_packet'), 1 ) ); sourceDb := comboSelectDatabase.Text; destDb := comboOtherDatabase.Text; end; - // Export to other window/host - if tohost then begin - target_version := remote_version; - max_allowed_packet := remote_max_allowed_packet; - win2export := appHandles[comboOtherHost.ItemIndex]; - sourceDb := comboSelectDatabase.Text; - if exportdb then begin - // Use original DB-name from source-server - destDb := comboSelectDatabase.Text; - end else begin - // Use existing DB-name on target-server - destDb := comboOtherHostDatabase.Items[comboOtherHostDatabase.ItemIndex]; - end; - end; - // MySQL has supported extended insert since 3.23. extended_insert := not (target_version = SQL_VERSION_ANSI); @@ -620,10 +505,10 @@ begin if tofile then begin wfs(f, '# --------------------------------------------------------'); - wfs(f, WideFormat('# %-30s%s', ['Host:', Mainform.MysqlConn.Connection.HostName])); + wfs(f, WideFormat('# %-30s%s', ['Host:', Mainform.Connection.HostName])); wfs(f, WideFormat('# %-30s%s', ['Database:', sourceDb])); - wfs(f, WideFormat('# %-30s%s', ['Server version:', Mainform.GetVar('SELECT VERSION()')])); - wfs(f, WideFormat('# %-30s%s', ['Server OS:', Mainform.GetVar('SHOW VARIABLES LIKE ' + esc('version_compile_os'), 1)])); + wfs(f, WideFormat('# %-30s%s', ['Server version:', Mainform.Connection.GetVar('SELECT VERSION()')])); + wfs(f, WideFormat('# %-30s%s', ['Server OS:', Mainform.Connection.GetVar('SHOW VARIABLES LIKE ' + esc('version_compile_os'), 1)])); wfs(f, WideFormat('# %-30s%s', ['Target compatibility:', comboTargetCompat.Text])); if extended_insert then begin @@ -638,71 +523,27 @@ begin {*** Set characterset to current one } - if Mainform.mysql_version > 40100 then - current_characterset := Mainform.GetVar( 'SHOW VARIABLES LIKE ' + esc('character_set_connection'), 1 ) - else if Mainform.mysql_version > 40000 then + if Mainform.Connection.ServerVersionInt > 40100 then + current_characterset := Mainform.Connection.GetVar( 'SHOW VARIABLES LIKE ' + esc('character_set_connection'), 1 ) + else if Mainform.Connection.ServerVersionInt > 40000 then // todo: test this, add charolation --> charset conversion table from 4.0 to 4.1+ - current_characterset := Mainform.GetVar( 'SHOW VARIABLES LIKE ' + esc('character_set'), 1 ) + current_characterset := Mainform.Connection.GetVar( 'SHOW VARIABLES LIKE ' + esc('character_set'), 1 ) else // todo: test this current_characterset := 'binary'; - {*** - Check if source and destination session is connected to the same server. - } - samehost := todb; - if tohost then begin - i := CreateGuid(sameuuid); - if i <> 0 then raise Exception.Create('Could not create a GUID.'); - sql := esc(appName + '_' + GuidToString(sameuuid)); - try - i := StrToInt(Mainform.GetVar('SELECT GET_LOCK(' + sql + ', 0)')); - if i <> 1 then raise Exception.Create('Could not create a server lock.'); - query := RemoteExecQuery( - win2export, - 'SELECT GET_LOCK(' + sql + ', 0)', - 'Checking for same server...' - ); - i := query.Fields[0].AsInteger; - query.Free; - if i <> 1 then samehost := true - else begin - query := RemoteExecQuery( - win2export, - 'SELECT RELEASE_LOCK(' + sql + ')', - 'Releasing remote lock...' - ); - end; - finally - Mainform.ExecuteNonQuery('SELECT RELEASE_LOCK(' + sql + ')'); - end; - end; - - {*** - Avoid destructive actions (DROP before CREATE) on same host and database. - } - if tohost and samehost then begin - if exportdb and (comboDatabase.ItemIndex = DB_DROP_CREATE) then raise Exception.Create('Aborted: selected action "database recreate" on same source/destination host would drop source database.'); - if exporttables and (comboTables.ItemIndex = TAB_DROP_CREATE) then begin - if sourceDb = destDb then raise Exception.Create('Aborted: selected action "tables recreate" on same source/destination host and database would drop source tables.'); - end; - end; - {*** Some actions which are only needed if we're not in OtherDatabase-mode: Set character set, create and use database. } - if tofile or tohost then + if tofile then begin // Switch to correct SQL_MODE so MySQL doesn't reject ANSI SQL if target_version = SQL_VERSION_ANSI then begin sql := makeConditionalStmt('SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=''ANSI,NO_BACKSLASH_ESCAPES''', 40101, tofile); sql := fixSQL( sql, target_version, target_cliwa ); - if tofile then - wfs(f, sql) - else if tohost then - RemoteExecNonQuery(win2export, sql ); + wfs(f, sql) end; {*** @@ -711,10 +552,7 @@ begin } sql := makeConditionalStmt('SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0', 40014, tofile); sql := fixSQL( sql, target_version, target_cliwa ); - if tofile then - wfs(f, sql) - else if tohost then - RemoteExecNonQuery(win2export, sql ); + wfs(f, sql); if exportdb then begin @@ -735,14 +573,12 @@ begin sql := 'DROP DATABASE IF EXISTS ' + destMask(destDb); if tofile then wfs(f, sql + ';') - else if tohost then - RemoteExecNonQuery(win2export, sql ); end; {*** CREATE statement for database plus database-switching } - if Mainform.mysql_version < 50002 then + if Mainform.Connection.ServerVersionInt < 50002 then begin sql := 'CREATE DATABASE '; if comboDatabase.ItemIndex = DB_CREATE_IGNORE then @@ -753,7 +589,7 @@ begin end else begin - sql := Mainform.GetVar( 'SHOW CREATE DATABASE ' + sourceMask(sourceDb), 1 ); + sql := Mainform.Connection.GetVar( 'SHOW CREATE DATABASE ' + sourceMask(sourceDb), 1 ); sql := fixNewlines(sql); if target_version = SQL_VERSION_ANSI then sql := StringReplace(sql, '`', '"', [rfReplaceAll]); @@ -764,9 +600,7 @@ begin end; sql := fixSQL( sql, target_version, target_cliwa ); if tofile then - wfs(f, sql + ';') - else if tohost then - RemoteExecNonQuery(win2export, sql); + wfs(f, sql + ';'); if exporttables then begin if tofile then @@ -829,93 +663,10 @@ begin createquery := createquery + '#' + crlf + crlf; end; - {*** - Let the server generate the CREATE TABLE statement if the version allows that - } - if Mainform.mysql_version >= 32320 then - begin - Query := Mainform.GetResults('SHOW CREATE TABLE ' + sourceMask(checkListTables.Items[i])); - sql := Query.Fields[1].AsWideString; - Query.Close; - FreeAndNil(Query); - sql := fixNewlines(sql); - sql := fixSQL( sql, target_version, target_cliwa ); - end - {*** - Generate CREATE TABLE statement by hand on old servers - } - else if Mainform.mysql_version < 32320 then begin - Query := Mainform.GetResults( 'SHOW COLUMNS FROM ' + sourceMask(checkListTables.Items[i])); - if tofile then - sql := 'CREATE TABLE ' + destMask(checkListTables.Items[i]) + ' (' + crlf - else - sql := sql + 'CREATE TABLE ' + destMask(destDb) + '.' + sourceMask(checkListTables.Items[i]) + ' (' + crlf; - for j := 1 to Query.Fieldcount do - begin - sql := sql + ' ' + destMask(Query.Fields[0].AsWideString) + ' ' + Query.Fields[1].AsWideString; - if Query.Fields[2].AsString <> 'YES' then - sql := sql + ' NOT NULL'; - if Query.Fields[4].AsWideString <> '' then - sql := sql + ' DEFAULT ''' + Query.Fields[4].AsWideString + ''''; - if Query.Fields[5].AsWideString <> '' then - sql := sql + ' ' + Query.Fields[5].AsWideString; - if j < Query.Fieldcount then - sql := sql + ',' + crlf; - end; - Query.Close; - FreeAndNil(Query); - - // Keys: - Query := Mainform.GetResults( 'SHOW KEYS FROM ' + sourceMask(checkListTables.Items[i])); - setLength(keylist, 0); - keystr := ''; - if Query.RecordCount > 0 then - keystr := ','; - - for j := 1 to Query.RecordCount do - begin - which := -1; - - for k:=0 to length(keylist)-1 do - begin - if keylist[k].Name = Query.Fields[2].AsString then // keyname exists! - which := k; - end; - if which = -1 then - begin - setlength(keylist, length(keylist)+1); - which := high(keylist); - keylist[which].Columns := TWideStringList.Create; - with keylist[which] do // set properties for new key - begin - Name := Query.Fields[2].AsString; - if Query.Fields[2].AsString = 'PRIMARY' then - _type := 'PRIMARY' - else if Query.FieldCount >= 10 then if Query.Fields[9].AsString = 'FULLTEXT' then - _type := 'FULLTEXT' - else if Query.Fields[1].AsString = '1' then - _type := '' - else if Query.Fields[1].AsString = '0' then - _type := 'UNIQUE'; - end; - end; - keylist[which].Columns.add(destMask(Query.Fields[4].AsWideString)); // add column(s) - Query.Next; - end; - Query.Close; - FreeAndNil(Query); - for k:=0 to high(keylist) do - begin - if k > 0 then - keystr := keystr + ','; - if keylist[k].Name = 'PRIMARY' then - keystr := keystr + crlf + ' PRIMARY KEY (' - else - keystr := keystr + crlf + ' ' + keylist[k]._type + ' KEY ' + destMask(keylist[k].Name) + ' ('; - keystr := keystr + implodestr(',', keylist[k].Columns) + ')'; - end; - sql := sql + keystr + crlf + ')'; - end; // mysql_version < 32320 + // Let the server generate the CREATE TABLE statement + sql := Mainform.Connection.GetVar('SHOW CREATE TABLE ' + sourceMask(checkListTables.Items[i]), 1); + sql := fixNewlines(sql); + sql := fixSQL( sql, target_version, target_cliwa ); if not tofile then Insert(destMask(destDb) + '.', sql, Pos('TABLE', sql) + 6); @@ -939,15 +690,8 @@ begin // Run CREATE TABLE on another Database else if todb then begin if comboTables.ItemIndex = TAB_DROP_CREATE then - Mainform.ExecUpdateQuery( dropquery ); - Mainform.ExecUpdateQuery( createquery ); - end - - // Run CREATE TABLE on another host - else if tohost then begin - if comboTables.ItemIndex = TAB_DROP_CREATE then - RemoteExecNonQuery(win2export, dropquery); - RemoteExecNonQuery(win2export, createquery); + Mainform.Connection.Query( dropquery ); + Mainform.Connection.Query( createquery ); end; barProgress.StepIt; @@ -961,16 +705,15 @@ begin // Set to mysql-readable char: DecimalSeparator := '.'; columnnames := ' ('; - Query := Mainform.GetResults( 'SHOW FIELDS FROM ' + sourceMask(checkListTables.Items[i])); - for k:=1 to Query.RecordCount do + Query := Mainform.Connection.GetResults( 'SHOW FIELDS FROM ' + sourceMask(checkListTables.Items[i])); + for k:=0 to Query.RecordCount-1 do begin if k>1 then columnnames := columnnames + ', '; - columnnames := columnnames + destMask(Query.Fields[0].AsWideString); + columnnames := columnnames + destMask(Query.Col(0)); Query.Next; end; columnnames := columnnames+')'; - Query.Close; FreeAndNil(Query); if tofile then @@ -986,23 +729,15 @@ begin if comboData.ItemIndex = DATA_TRUNCATE_INSERT then begin if tofile then - begin - wfs(f, 'TRUNCATE TABLE ' + destMask(checkListTables.Items[i]) + ';'); - end + wfs(f, 'TRUNCATE TABLE ' + destMask(checkListTables.Items[i]) + ';') else if todb then - begin - Mainform.ExecUpdateQuery('TRUNCATE TABLE ' + sourceMask(destDb) + '.' + checkListTables.Items[i]); - end - else if tohost then - begin - RemoteExecNonQuery(win2export, 'TRUNCATE TABLE ' + destMask(destDb) + '.' + checkListTables.Items[i]); - end; + Mainform.Connection.Query('TRUNCATE TABLE ' + sourceMask(destDb) + '.' + checkListTables.Items[i]); end; // Set rows per step limit and detect total row count // Be sure to do this step before the table is locked! limit := 5000; - RecordCount_all := MakeInt(Mainform.GetNamedVar('SHOW TABLE STATUS LIKE '+esc(checkListTables.Items[i]), 'Rows')); + RecordCount_all := MakeInt(Mainform.Connection.GetVar('SHOW TABLE STATUS LIKE '+esc(checkListTables.Items[i]), 'Rows')); if RecordCount_all = 0 then begin if tofile then @@ -1020,16 +755,10 @@ begin end else if todb then begin - Mainform.ExecUpdateQuery( 'LOCK TABLES ' + sourceMask(destDb) + '.' + sourceMask(checkListTables.Items[i])+ ' WRITE, ' + sourceMask(sourceDb) + '.' + sourceMask(checkListTables.Items[i])+ ' WRITE'); + Mainform.Connection.Query( 'LOCK TABLES ' + sourceMask(destDb) + '.' + sourceMask(checkListTables.Items[i])+ ' WRITE, ' + sourceMask(sourceDb) + '.' + sourceMask(checkListTables.Items[i])+ ' WRITE'); if target_version > 40000 then - Mainform.ExecUpdateQuery( 'ALTER TABLE ' + sourceMask(destDb) + '.' + sourceMask(checkListTables.Items[i])+ ' DISABLE KEYS' ); + Mainform.Connection.Query( 'ALTER TABLE ' + sourceMask(destDb) + '.' + sourceMask(checkListTables.Items[i])+ ' DISABLE KEYS' ); end - else if tohost then - begin - RemoteExecNonQuery(win2export, 'LOCK TABLES ' + destMask(destDb) + '.' + destMask(checkListTables.Items[i]) + ' WRITE'); - if target_version > 40000 then - RemoteExecNonQuery(win2export, 'ALTER TABLE ' + destMask(destDb) + '.' + destMask(checkListTables.Items[i]) + ' DISABLE KEYS'); - end; end; offset := 0; @@ -1056,7 +785,7 @@ begin end; // Execute SELECT - Query := Mainform.GetResults( sql_select ); + Query := Mainform.Connection.GetResults( sql_select ); insertquery := ''; valuescount := 0; @@ -1087,26 +816,24 @@ begin end; thesevalues := '('; - for k := 0 to Query.fieldcount-1 do + for k := 0 to Query.ColumnCount-1 do begin - if Query.Fields[k].IsNull then + if Query.IsNull(k) then value := 'NULL' else - case Query.Fields[k].DataType of - ftInteger, ftSmallint, ftWord: - value := Query.Fields[k].AsWideString; - ftBoolean: - value := esc( BoolToStr( Query.Fields[k].AsBoolean ) ); - ftBlob: - if Query.Fields[k].AsString <> '' then - value := '0x' + BinToWideHex(Query.Fields[k].AsString) + case Query.DataType(k).Category of + dtcInteger: + value := Query.Col(k); + dtcBinary: + if Query.Col(k) <> '' then + value := '0x' + BinToWideHex(Query.Col(k)) else value := esc(''); else - value := esc( Query.Fields[k].AsWideString, False, target_version ); + value := esc( Query.Col(k), False, target_version ); end; thesevalues := thesevalues + value; - if k < Query.Fieldcount-1 then + if k < Query.ColumnCount-1 then thesevalues := thesevalues + ','; end; thesevalues := thesevalues + ')'; @@ -1141,15 +868,12 @@ begin insertquery := insertquery + ';'; wfs(f, insertquery) end else if todb then - Mainform.ExecUpdateQuery(insertquery) - else if tohost then - RemoteExecNonQuery(win2export, insertquery); + Mainform.Connection.Query(insertquery); if donext then Query.Next; donext := true; insertquery := ''; end; - Query.Close; FreeAndNil(Query); end; // Set back to local setting: @@ -1164,14 +888,8 @@ begin else if todb then begin if target_version > 40000 then - Mainform.ExecUpdateQuery( 'ALTER TABLE ' + sourceMask(destDb) + '.' + sourceMask(checkListTables.Items[i]) + ' ENABLE KEYS' ); - Mainform.ExecUpdateQuery( 'UNLOCK TABLES' ); - end - else if tohost then - begin - if target_version > 40000 then - RemoteExecNonQuery(win2export, 'ALTER TABLE ' + destMask(destDb) + '.' + destMask(checkListTables.Items[i]) + ' ENABLE KEYS'); - RemoteExecNonQuery(win2export, 'UNLOCK TABLES'); + Mainform.Connection.Query( 'ALTER TABLE ' + sourceMask(destDb) + '.' + sourceMask(checkListTables.Items[i]) + ' ENABLE KEYS' ); + Mainform.Connection.Query( 'UNLOCK TABLES' ); end; end; barProgress.StepIt; @@ -1189,14 +907,11 @@ begin end; // Restore old value for SQL_MODE - if (tofile or tohost) and (target_version = SQL_VERSION_ANSI) then + if tofile and (target_version = SQL_VERSION_ANSI) then begin sql := makeConditionalStmt('SET SQL_MODE=@OLD_SQL_MODE', 40101, tofile); sql := fixSql(sql, target_version, target_cliwa); - if tofile then - wfs(f, sql) - else if tohost then - RemoteExecNonQuery(win2export, sql ); + wfs(f, sql); end; {*** @@ -1206,9 +921,7 @@ begin sql := makeConditionalStmt('SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS', 40014, tofile); sql := fixSQL( sql, target_version, target_cliwa ); if tofile then - wfs(f, sql) - else if tohost then - RemoteExecNonQuery(win2export, sql ); + wfs(f, sql); FINALLY Mainform.TemporaryDatabase := ''; @@ -1328,10 +1041,6 @@ begin btnDirectoryBrowse.Enabled := False; comboOtherDatabase.Enabled := False; comboOtherDatabase.Color := DisabledColor; - comboOtherHost.Enabled := False; - comboOtherHost.Color := DisabledColor; - comboOtherHostDatabase.Enabled := False; - comboOtherHostDatabase.Color := DisabledColor; // Silence compiler warning ControlToFocus := EditFileName; @@ -1350,11 +1059,6 @@ begin comboOtherDatabase.Enabled := True; comboOtherDatabase.Color := EnabledColor; ControlToFocus := comboOtherDatabase; - end else if radioOtherHost.Checked then begin - comboOtherHost.Enabled := True; - comboOtherHost.Color := EnabledColor; - comboOtherHostDatabase.Color := EnabledColor; - ControlToFocus := comboOtherHost; end; if ControlToFocus.CanFocus then ControlToFocus.SetFocus; @@ -1365,7 +1069,7 @@ end; procedure TExportSQLForm.validateControls(Sender: TObject); begin - cbxDatabase.Enabled := cbxStructure.Checked and (radioFile.Checked or radioDirectory.Checked or radioOtherHost.Checked); + cbxDatabase.Enabled := cbxStructure.Checked and (radioFile.Checked or radioDirectory.Checked); comboDatabase.Enabled := cbxDatabase.Enabled and cbxDatabase.Checked; cbxTables.Enabled := cbxStructure.Checked; @@ -1373,9 +1077,6 @@ begin comboData.Enabled := cbxData.Checked; - // Should possible be in validateRadioControls() but is dependent on properties decided above. - comboOtherHostDatabase.Enabled := not (cbxDatabase.Enabled and cbxDatabase.Checked); - // Prevent choosing export of db struct + data but no table struct. if cbxData.Checked then begin if Sender = cbxTables then cbxDatabase.Checked := cbxDatabase.Checked and cbxTables.Checked @@ -1473,55 +1174,6 @@ begin end; -procedure TExportSQLForm.radioOtherHostClick(Sender: TObject); -var - list: TWindowDataArray; - i, k: integer; -begin - // Check if all the heidisql windows are still alive. - CheckForCrashedWindows; - - // Fetch list of heidisql windows. - list := GetWindowList; - - // Fill list of hosts. - comboOtherHost.Items.Clear; - SetLength(appHandles, High(list)); - k := 0; - for i := 0 to High(list) do with list[i] do begin - // Do not include current window. - if appHandle <> MainForm.Handle then begin - // Do not include non-connected windows. - if connected then begin - if namePostfix <> 0 then name := name + Format(' (%d)', [namePostFix]); - comboOtherHost.Items.Add(name); - appHandles[k] := appHandle; - k := k + 1; - end; - end; - end; - - // Abort if no other windows. - if comboOtherHost.Items.Count = 0 then begin - MessageDLG('You need at least two open connection-windows to enable this option.', mtError, [mbOK], 0); - radioFile.Checked := true; - abort; - end; - - // Select first host and call change event - comboOtherHost.ItemIndex := 0; - comboOtherHost.OnSelect(comboOtherHost); - - // De-select database structure to enable database dropdown box. - cbxDatabase.Checked := false; - - validateRadioControls(Sender); - validateControls(Sender); - generateExampleSql; -end; - - - {*** Save settings in registry, should be called just before closing the form, but not when Cancel was pressed. @@ -1547,9 +1199,7 @@ begin if radioDirectory.checked then OutputTo := OUTPUT_DIR else if radioOtherDatabase.checked then - OutputTo := OUTPUT_DB - else if radioOtherHost.checked then - OutputTo := OUTPUT_HOST; + OutputTo := OUTPUT_DB; WriteInteger(REGNAME_EXP_TARGET, OutputTo ); WriteString(REGNAME_EXP_DESTDB, Utf8Encode(comboOtherDatabase.Text)); WriteInteger(REGNAME_EXP_WINWIDTH, Width ); diff --git a/source/helpers.pas b/source/helpers.pas index fc284fe2..d59c0c41 100644 --- a/source/helpers.pas +++ b/source/helpers.pas @@ -9,9 +9,9 @@ unit helpers; interface uses Classes, SysUtils, Graphics, db, clipbrd, dialogs, - forms, controls, ShellApi, checklst, windows, ZDataset, ZAbstractDataset, + forms, controls, ShellApi, checklst, windows, shlobj, ActiveX, WideStrUtils, VirtualTrees, SynRegExpr, Messages, WideStrings, - TntCheckLst, Registry, SynEditHighlighter, mysql_structures, DateUtils; + TntCheckLst, Registry, SynEditHighlighter, mysql_connection, mysql_structures, DateUtils; type @@ -45,7 +45,7 @@ type property AsWideString: WideString read GetAsWideString write SetAsWideString; end; - // Structures for result grids, mapped from a TDataset to some handy VirtualTree structure + // Structures for result grids, mapped from a TMySQLQuery to some handy VirtualTree structure TGridCell = record Text: WideString; NewText: WideString; // Used to create UPDATE clauses with needed columns @@ -103,23 +103,6 @@ type // General purpose editing status flag TEditingStatus = (esUntouched, esModified, esDeleted, esAddedUntouched, esAddedModified, esAddedDeleted); - TDeferDataSet = class; - TAsyncPostRunner = procedure(ds: TDeferDataSet) of object; - - TDeferDataSet = class(TZQuery) - private - callback: TAsyncPostRunner; - kind: Integer; - protected - procedure InternalPost; override; - procedure InternalRefresh; override; - public - constructor Create(AOwner: TComponent; PostCallback: TAsyncPostRunner); reintroduce; - procedure ExecSQL; override; - procedure DoAsync; - procedure DoAsyncExecSql; - end; - {$I const.inc} function implodestr(seperator: WideString; a: TWideStringList) :WideString; @@ -175,7 +158,6 @@ type function getFirstWord( text: String ): String; function ConvertWindowsCodepageToMysqlCharacterSet(codepage: Cardinal): string; function LastPos(needle: WideChar; haystack: WideString): Integer; - function ConvertServerVersion( Version: Integer ): String; function FormatByteNumber( Bytes: Int64; Decimals: Byte = 1 ): String; Overload; function FormatByteNumber( Bytes: String; Decimals: Byte = 1 ): String; Overload; function FormatTimeNumber( Seconds: Cardinal ): String; @@ -184,7 +166,7 @@ type procedure SetVTSelection( VT: TVirtualStringTree; Selected: TWideStringList ); function Pos2(const Needle, HayStack: string; const StartPos: Integer) : Integer; function GetTempDir: String; - function GetDBObjectType( TableStatus: TFields ): TListNodeType; + function GetDBObjectType(TableStatus: TMySQLQuery): TListNodeType; procedure SetWindowSizeGrip(hWnd: HWND; Enable: boolean); procedure SaveUnicodeFile(Filename: String; Text: WideString); function CreateUnicodeFileStream(Filename: String): TFileStream; @@ -213,8 +195,7 @@ type procedure SelectNode(VT: TVirtualStringTree; Node: PVirtualNode); overload; function DateBackFriendlyCaption(d: TDateTime): String; procedure InheritFont(AFont: TFont); - function FieldContent(ds: TDataSet; ColName: WideString): WideString; - function GetTableSize(ds: TDataSet): Int64; + function GetTableSize(Results: TMySQLQuery): Int64; var MainReg : TRegistry; @@ -931,7 +912,7 @@ end; {*** - Converts a TDataSet to CSV-values. + Converts grid contents to CSV-values. @param Grid Object which holds data to export @param string Field-separator @param string Field-encloser @@ -1022,7 +1003,7 @@ end; {*** - Converts a TDataSet to XML. + Converts grid contents to XML. @param Grid Object which holds data to export @param string Text used as root-element } @@ -1099,7 +1080,7 @@ end; {*** - Converts a TDataSet to XML. + Converts grid contents to XML. @param Grid Object which holds data to export @param string Text used as tablename in INSERTs } @@ -2172,21 +2153,6 @@ begin end; -{** - Convert integer version to real version string -} -function 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; - - {** Format a filesize to automatically use the best fitting expression 16 100 000 Bytes -> 16,1 MB @@ -2325,7 +2291,7 @@ end; // Tell type of db object (table|view) by a given row from a SHOW TABLE STATUS result -function GetDBObjectType( TableStatus: TFields ): TListNodeType; +function GetDBObjectType(TableStatus: TMySQLQuery): TListNodeType; var t: String; begin @@ -2338,8 +2304,8 @@ begin "Views bla references invalid..." } Result := lntTable; - if TableStatus.FindField('Type') <> nil then begin - t := TableStatus.FindField('Type').AsString; + if TableStatus.ColExists('Type') then begin + t := TableStatus.Col('Type'); if t = 'BASE TABLE' then Result := lntTable else if t = 'VIEW' then @@ -2350,14 +2316,14 @@ begin Result := lntProcedure; end else begin if - TableStatus[1].IsNull and // Engine column is NULL for views - TableStatus[2].IsNull and - (Pos('VIEW', UpperCase(TableStatus.FieldByName(DBO_COMMENT).AsWideString)) > 0) + TableStatus.IsNull(1) and // Engine column is NULL for views + TableStatus.IsNull(2) and + (Pos('VIEW', UpperCase(TableStatus.Col(DBO_COMMENT))) > 0) then Result := lntView; if - TableStatus[1].IsNull and - TableStatus[2].IsNull and - (Pos('MARKED AS CRASHED', UpperCase(TableStatus.FieldByName(DBO_COMMENT).AsWideString)) > 0) + TableStatus.IsNull(1) and + TableStatus.IsNull(2) and + (Pos('MARKED AS CRASHED', UpperCase(TableStatus.Col(DBO_COMMENT))) > 0) then Result := lntCrashedTable; end; end; @@ -3080,68 +3046,17 @@ begin end; -// Fetch content from a row cell, avoiding NULLs to cause AVs -function FieldContent(ds: TDataSet; ColName: WideString): WideString; -begin - Result := ''; - if (ds.FindField(colName) <> nil) and (not ds.FindField(ColName).IsNull) then - Result := ds.FieldByName(ColName).AsWideString; -end; - - -function GetTableSize(ds: TDataSet): Int64; +function GetTableSize(Results: TMySQLQuery): Int64; var d, i: String; begin - d := FieldContent(ds, 'Data_length'); - i := FieldContent(ds, 'Index_length'); + d := Results.Col('Data_length', True); + i := Results.Col('Index_length', True); if (d = '') or (i = '') then Result := -1 else Result := MakeInt(d) + MakeInt(i); end; -procedure TDeferDataSet.InternalPost; -begin - kind := 1; - if @callback = nil then DoAsync - else callback(self); -end; - -procedure TDeferDataSet.InternalRefresh; -begin - kind := 3; - if @callback = nil then DoAsync - else callback(self); -end; - -procedure TDeferDataSet.ExecSql; -begin - kind := 2; - if @callback = nil then DoAsync - else callback(self); -end; - -constructor TDeferDataSet.Create(AOwner: TComponent; PostCallback: TAsyncPostRunner); -begin - callback := PostCallback; - inherited Create(AOwner); -end; - -procedure TDeferDataSet.DoAsync; -begin - case kind of - 1: inherited InternalPost; - 2: inherited ExecSQL; - 3: inherited InternalRefresh; - end; -end; - -procedure TDeferDataSet.DoAsyncExecSql; -begin - inherited ExecSql; -end; - - end. diff --git a/source/insertfiles.pas b/source/insertfiles.pas index 6261acd0..6f820d1a 100644 --- a/source/insertfiles.pas +++ b/source/insertfiles.pas @@ -4,7 +4,8 @@ interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, - StdCtrls, ComCtrls, ImgList, Buttons, ShellApi, Math, insertfiles_progress; + StdCtrls, ComCtrls, ImgList, Buttons, ShellApi, Math, insertfiles_progress, + mysql_connection; type TCol = record Name : String; // for displaying in lists @@ -105,15 +106,15 @@ end; { Read tables from selected DB } procedure TfrmInsertFiles.ComboBoxDBsChange(Sender: TObject); var - ds: TDataset; + Results: TMySQLQuery; begin // read tables from db ComboBoxTables.Items.Clear; - ds := Mainform.FetchDbTableList(ComboBoxDBs.Text); - while not ds.Eof do begin - if GetDBObjectType(ds.Fields) in [lntTable, lntView] then - ComboBoxTables.Items.Add(ds.FieldByName(DBO_NAME).AsString); - ds.Next; + Results := Mainform.FetchDbTableList(ComboBoxDBs.Text); + while not Results.Eof do begin + if GetDBObjectType(Results) in [lntTable, lntView] then + ComboBoxTables.Items.Add(Results.Col(DBO_NAME)); + Results.Next; end; if ComboBoxTables.Items.Count > 0 then ComboBoxTables.ItemIndex := 0; @@ -123,22 +124,20 @@ end; { Show Columns from selected table } procedure TfrmInsertFiles.ComboBoxTablesChange(Sender: TObject); var - i : Integer; - ds : TDataSet; + Results: TMySQLQuery; begin setlength(cols, 0); if ComboBoxTables.ItemIndex > -1 then begin - ds := Mainform.GetResults('SHOW FIELDS FROM '+mainform.mask(ComboBoxDBs.Text)+'.'+mainform.mask(ComboBoxTables.Text)); - for i:=1 to ds.RecordCount do begin + Results := Mainform.Connection.GetResults('SHOW FIELDS FROM '+mainform.mask(ComboBoxDBs.Text)+'.'+mainform.mask(ComboBoxTables.Text)); + while not Results.Eof do begin setlength(cols, length(cols)+1); - cols[length(cols)-1].Name := ds.Fields[0].AsString; - cols[length(cols)-1].isBLOB := (pos('blob', lowercase(ds.Fields[1].AsString)) > 0) or (pos('text', lowercase(ds.Fields[1].AsString)) > 0); + cols[length(cols)-1].Name := Results.Col(0); + cols[length(cols)-1].isBLOB := (pos('blob', lowercase(Results.Col(1))) > 0) or (pos('text', lowercase(Results.Col(1))) > 0); cols[length(cols)-1].Value := 'NULL'; cols[length(cols)-1].Quote := false; - ds.Next; + Results.Next; end; - ds.Close; - FreeAndNil(ds); + FreeAndNil(Results); DisplayColumns(self); end; end; diff --git a/source/insertfiles_progress.pas b/source/insertfiles_progress.pas index 3a2d7757..0c498690 100644 --- a/source/insertfiles_progress.pas +++ b/source/insertfiles_progress.pas @@ -4,7 +4,7 @@ interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, - StdCtrls, ComCtrls, ExtCtrls, Db, ZDataset; + StdCtrls, ComCtrls, ExtCtrls, mysql_connection; type TfrmInsertFilesProgress = class(TForm) @@ -59,17 +59,13 @@ var dt: TDateTime; y, m, d, h, mi, s, ms: Word; FileStream: TFileStream; - zq: TDeferDataSet; - zqSqlIdx: Integer; readBuf: String; bytesRead: Integer; - data: WideString; + sql, data: WideString; begin Timer1.Enabled := false; screen.Cursor := crHourglass; ProgressBar1.Max := TfrmInsertFiles(FInsertFilesForm).ListViewFiles.Items.Count; - zq := TDeferDataSet.Create(nil, Mainform.RunAsyncPost); - zq.Connection := Mainform.Conn.MysqlConn; TRY @@ -83,44 +79,16 @@ begin filename := ListViewFiles.Items[i].Caption; lblFilename.Caption := mince(filename, 30) + ' ('+FormatNumber(ListViewFiles.Items[i].SubItems[0])+' KB)'; lblFilename.Repaint; - zq.ParamCheck := true; - zq.SQL.Clear; - zq.SQL.Add( 'INSERT INTO '+mainform.mask(ComboBoxDBs.Text)+'.'+mainform.mask(ComboBoxTables.Text) + - ' (' + mainform.mask(ComboBoxColumns.Text) ); + sql := 'INSERT INTO '+mainform.mask(ComboBoxDBs.Text)+'.'+mainform.mask(ComboBoxTables.Text) + + ' (' + mainform.mask(ComboBoxColumns.Text); lblOperation.caption := 'Inserting data ...'; lblOperation.Repaint; for j:=0 to length(cols)-1 do begin if cols[j].Name = ComboBoxColumns.Text then continue; - zq.SQL.Add( ', ' + mainform.mask(cols[j].Name) ); + sql := sql + ', ' + mainform.mask(cols[j].Name); end; - zqSqlIdx := zq.SQL.Add( ') VALUES (:STREAM, ' ); - - for j:=0 to length(cols)-1 do - begin - if cols[j].Name = ComboBoxColumns.Text then - continue; - Value := cols[j].Value; - if pos('%', Value) > 0 then - begin - //Value := stringreplace(Value, '%filesize%', inttostr(size), [rfReplaceAll]); - Value := stringreplace(Value, '%filename%', ExtractFileName(filename), [rfReplaceAll]); - Value := stringreplace(Value, '%filepath%', ExtractFilePath(filename), [rfReplaceAll]); - FileAge(filename, dt); - DecodeDate(dt, y, m, d); - DecodeTime(dt, h, mi, s, ms); - Value := stringreplace(Value, '%filedate%', Format('%.4d-%.2d-%.2d', [y,m,d]), [rfReplaceAll]); - Value := stringreplace(Value, '%filedatetime%', Format('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', [y,m,d,h,mi,s]), [rfReplaceAll]); - Value := stringreplace(Value, '%filetime%', Format('%.2d:%.2d:%.2d', [h,mi,s]), [rfReplaceAll]); - end; - if cols[j].Quote then - Value := esc(Value); - zq.SQL.Add( Value + ', ' ); - end; - // Strip last komma + space + CR + LF - zq.SQL.Text := copy( zq.SQL.Text, 1, length(zq.SQL.Text)-4 ); - zq.SQL.Add( ')' ); try lblOperation.caption := 'Reading file ...'; lblOperation.Repaint; @@ -141,7 +109,6 @@ begin SetLength(readBuf, bytesRead div SizeOf(Char)); data := data + BinToWideHex(readBuf); end; - zq.SQL[zqSqlIdx] := StringReplace(zq.SQL[zqSqlIdx], ':STREAM', data, []); finally FileStream.Free; end; @@ -149,7 +116,33 @@ begin MessageDlg( 'Error reading file:' + CRLF + filename, mtError, [mbOK], 0 ); break; end; - zq.ExecSql; + sql := sql + ') VALUES ('+data+', '; + + for j:=0 to length(cols)-1 do + begin + if cols[j].Name = ComboBoxColumns.Text then + continue; + Value := cols[j].Value; + if pos('%', Value) > 0 then + begin + //Value := stringreplace(Value, '%filesize%', inttostr(size), [rfReplaceAll]); + Value := stringreplace(Value, '%filename%', ExtractFileName(filename), [rfReplaceAll]); + Value := stringreplace(Value, '%filepath%', ExtractFilePath(filename), [rfReplaceAll]); + FileAge(filename, dt); + DecodeDate(dt, y, m, d); + DecodeTime(dt, h, mi, s, ms); + Value := stringreplace(Value, '%filedate%', Format('%.4d-%.2d-%.2d', [y,m,d]), [rfReplaceAll]); + Value := stringreplace(Value, '%filedatetime%', Format('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', [y,m,d,h,mi,s]), [rfReplaceAll]); + Value := stringreplace(Value, '%filetime%', Format('%.2d:%.2d:%.2d', [h,mi,s]), [rfReplaceAll]); + end; + if cols[j].Quote then + Value := esc(Value); + sql := sql + Value + ', '; + end; + // Strip last comma + space + sql := copy(sql, 1, length(sql)-2); + sql := sql + ')'; + Mainform.Connection.Query(sql); lblOperation.caption := 'Freeing memory ...'; lblOperation.Repaint; ProgressBar1.StepIt; @@ -158,7 +151,6 @@ begin end; FINALLY - FreeAndNil(zq); screen.Cursor := crDefault; Close(); END; diff --git a/source/loaddata.pas b/source/loaddata.pas index 74473080..40aeebe6 100644 --- a/source/loaddata.pas +++ b/source/loaddata.pas @@ -11,7 +11,7 @@ interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, comctrls, Buttons, CheckLst, PngSpeedButton, - WideStrings, TntCheckLst, TntStdCtrls, db, SynRegExpr; + WideStrings, TntCheckLst, TntStdCtrls, mysql_connection, SynRegExpr; type Tloaddataform = class(TForm) @@ -68,7 +68,7 @@ type procedure btnColDownClick(Sender: TObject); private { Private declarations } - dsCharsets: TDataset; + dsCharsets: TMySQLQuery; public { Public declarations } end; @@ -138,7 +138,7 @@ end; procedure Tloaddataform.comboDatabaseChange(Sender: TObject); var count, i, selCharsetIndex, v: Integer; - ds: TDataset; + ds: TMySQLQuery; seldb, seltable, dbcreate: WideString; rx: TRegExpr; DefCharset: String; @@ -149,8 +149,8 @@ begin seltable := Mainform.SelectedTable.Text; ds := Mainform.FetchDbTableList(comboDatabase.Text); while not ds.Eof do begin - if GetDBObjectType(ds.Fields) in [lntTable, lntView] then - comboTable.Items.Add(ds.FieldByName(DBO_NAME).AsWideString); + if GetDBObjectType(ds) in [lntTable, lntView] then + comboTable.Items.Add(ds.Col(DBO_NAME)); count := comboTable.Items.Count-1; if (comboDatabase.Text = seldb) and (comboTable.Items[count] = seltable) then comboTable.ItemIndex := count; @@ -164,14 +164,14 @@ begin selCharsetIndex := comboCharset.ItemIndex; comboCharset.Enabled := False; comboCharset.Clear; - v := Mainform.mysql_version; + v := Mainform.Connection.ServerVersionInt; if ((v >= 50038) and (v < 50100)) or (v >= 50117) then begin if dsCharsets = nil then - dsCharsets := Mainform.GetResults('SHOW CHARSET'); + dsCharsets := Mainform.Connection.GetResults('SHOW CHARSET'); comboCharset.Enabled := True; // Detect db charset DefCharset := 'Let server/database decide'; - dbcreate := Mainform.GetVar('SHOW CREATE DATABASE '+Mainform.mask(comboDatabase.Text), 1); + dbcreate := Mainform.Connection.GetVar('SHOW CREATE DATABASE '+Mainform.mask(comboDatabase.Text), 1); rx := TRegExpr.Create; rx.ModifierG := True; rx.Expression := 'CHARACTER SET (\w+)'; @@ -179,9 +179,10 @@ begin DefCharset := DefCharset + ' ('+rx.Match[1]+')'; comboCharset.Items.Add(DefCharset); dsCharsets.First; - for i:=1 to dsCharsets.RecordCount do begin - comboCharset.Items.Add(dsCharsets.Fields[1].AsWideString + ' ('+dsCharsets.Fields[0].AsWideString+')'); - if dsCharsets.Fields[0].AsWideString = 'utf8' then begin + while not dsCharsets.Eof do begin + comboCharset.Items.Add(dsCharsets.Col(1) + ' ('+dsCharsets.Col(0)+')'); + if dsCharsets.Col(0) = 'utf8' then begin + i := comboCharset.Items.Count-1; comboCharset.Items[i] := comboCharset.Items[i] + ' - '+APPNAME+' output'; if selCharsetIndex = -1 then selCharsetIndex := i; @@ -197,22 +198,11 @@ end; procedure Tloaddataform.comboTableChange(Sender: TObject); -var - i : Integer; - ds : TDataSet; begin // fill columns: chklistColumns.Items.Clear; - if (comboDatabase.Text <> '') and (comboTable.Text <> '') then begin - ds := Mainform.GetResults( 'SHOW FIELDS FROM ' + mainform.mask(comboDatabase.Text) + '.' + mainform.mask(comboTable.Text)); - for i:=1 to ds.RecordCount do - begin - chklistColumns.Items.Add(ds.Fields[0].AsWideString); - ds.Next; - end; - ds.Close; - FreeAndNil(ds); - end; + if (comboDatabase.Text <> '') and (comboTable.Text <> '') then + chklistColumns.Items.Text := Mainform.Connection.GetCol('SHOW FIELDS FROM ' + mainform.mask(comboDatabase.Text) + '.' + mainform.mask(comboTable.Text)).Text; // select all: ToggleCheckListBox( chklistColumns, True ); @@ -267,7 +257,7 @@ begin if comboCharset.ItemIndex > 0 then begin dsCharsets.RecNo := comboCharset.ItemIndex; - query := query + 'CHARACTER SET '+dsCharsets.Fields[0].AsString+' '; + query := query + 'CHARACTER SET '+dsCharsets.Col(0)+' '; end; // Fields: @@ -300,7 +290,7 @@ begin // if col.Count < ColumnsCheckListBox.Items.Count then query := query + '(' + implodestr(',', col) + ')'; - Mainform.ExecUpdateQuery(query); + Mainform.Connection.Query(query); close; end; diff --git a/source/main.dfm b/source/main.dfm index ab83e20a..6b4c46e3 100644 --- a/source/main.dfm +++ b/source/main.dfm @@ -1591,13 +1591,6 @@ object MainForm: TMainForm Action = actExportData end end - object menuWindow: TMenuItem - Caption = '&Window' - OnClick = menuWindowClick - object miFake: TMenuItem - Caption = 'fake item - see notes in menuWindowClick()' - end - end object Help1: TMenuItem Tag = 22 Caption = '&Help' @@ -6464,7 +6457,6 @@ object MainForm: TMainForm end end object TimerConnected: TTimer - Enabled = False OnTimer = TimerConnectedTimer Left = 103 Top = 269 @@ -6517,13 +6509,6 @@ object MainForm: TMainForm Left = 40 Top = 200 end - object ZSQLMonitor1: TZSQLMonitor - Active = True - MaxTraceCount = 100 - OnLogTrace = ZSQLMonitor1LogTrace - Left = 104 - Top = 304 - end object popupDbGridHeader: TPopupMenu AutoHotkeys = maManual AutoLineReduction = maManual diff --git a/source/main.pas b/source/main.pas index 22bc1519..0b80dac6 100644 --- a/source/main.pas +++ b/source/main.pas @@ -10,20 +10,19 @@ unit Main; interface uses - Synchronization, - Communication, Windows, SysUtils, Classes, Graphics, Forms, Controls, Menus, StdCtrls, Dialogs, Buttons, Messages, ExtCtrls, ComCtrls, StdActns, - ActnList, ImgList, ShellApi, ToolWin, Clipbrd, db, - SynMemo, synedit, SynEditTypes, ZDataSet, ZSqlProcessor, - sqlhelp, MysqlQueryThread, VirtualTrees, + ActnList, ImgList, ShellApi, ToolWin, Clipbrd, + SynMemo, synedit, SynEditTypes, + sqlhelp, VirtualTrees, DateUtils, PngImageList, TableTools, View, Usermanager, SelectDBObject, Widestrings, ShlObj, SynEditMiscClasses, SynEditSearch, - SynCompletionProposal, ZSqlMonitor, SynEditHighlighter, SynHighlighterSQL, - TntStdCtrls, Tabs, SynUnicode, mysqlconn, EditVar, helpers, queryprogress, - mysqlquery, createdatabase, table_editor, SynRegExpr, - WideStrUtils, ZDbcLogging, ExtActns, CommCtrl, routine_editor, options, - Contnrs, PngSpeedButton, connections, SynEditKeyCmds, exportsql; + SynCompletionProposal, SynEditHighlighter, SynHighlighterSQL, + TntStdCtrls, Tabs, SynUnicode, EditVar, helpers, + createdatabase, table_editor, SynRegExpr, + WideStrUtils, ExtActns, CommCtrl, routine_editor, options, + Contnrs, PngSpeedButton, connections, SynEditKeyCmds, exportsql, + mysql_connection, mysql_api; type @@ -136,8 +135,6 @@ type actLoadSQL: TAction; ImportSQL1: TMenuItem; menuConnections: TPopupMenu; - menuWindow: TMenuItem; - miFake: TMenuItem; menuBugtracker: TMenuItem; menuFeaturetracker: TMenuItem; menuDownload: TMenuItem; @@ -309,7 +306,6 @@ type InsertfilesintoBLOBfields3: TMenuItem; N19: TMenuItem; setNULL1: TMenuItem; - ZSQLMonitor1: TZSQLMonitor; menuExporttables: TMenuItem; popupDbGridHeader: TPopupMenu; SynCompletionProposal: TSynCompletionProposal; @@ -458,7 +454,6 @@ type procedure setDefaultWindowConfig; procedure actCreateTableExecute(Sender: TObject); procedure actCreateViewExecute(Sender: TObject); - procedure menuWindowClick(Sender: TObject); procedure focusWindow(Sender: TObject); procedure menuConnectionsPopup(Sender: TObject); procedure actExitApplicationExecute(Sender: TObject); @@ -518,13 +513,7 @@ type procedure actSQLhelpExecute(Sender: TObject); procedure actUpdateCheckExecute(Sender: TObject); procedure actWebbrowse(Sender: TObject); - function ExecuteRemoteQuery(sender: THandle; query: string): TDataSet; - procedure ExecuteRemoteNonQuery(sender: THandle; query: string); procedure FindDialogQueryFind(Sender: TObject); - procedure HandleWMComplete(var msg: TMessage); message WM_COMPLETED; - procedure HandleWMCopyData(var msg: TWMCopyData); message WM_COPYDATA; - procedure HandleWMProcessLog(var msg: TMessage); message WM_PROCESSLOG; - procedure HandleWMRefill(var msg: TMessage); message WM_REFILL_SPAREBUF; procedure ReplaceDialogQueryFind(Sender: TObject); procedure ReplaceDialogQueryReplace(Sender: TObject); procedure actCopyAsSQLExecute(Sender: TObject); @@ -557,7 +546,7 @@ type procedure EnsureChunkLoaded(Sender: TBaseVirtualTree; Node: PVirtualNode; FullWidth: Boolean = False); procedure DiscardNodeData(Sender: TVirtualStringTree; Node: PVirtualNode); procedure viewdata(Sender: TObject); - procedure LogSQL(msg: WideString = ''; comment: Boolean = true ); + procedure LogSQL(Msg: WideString; Category: TMySQLLogCategory=lcInfo); procedure CheckUptime; procedure KillProcess(Sender: TObject); procedure ExecSQLClick(Sender: TObject; Selection: Boolean = false; @@ -585,21 +574,8 @@ type procedure popupDataGridPopup(Sender: TObject); procedure InsertDate(Sender: TObject); procedure setNULL1Click(Sender: TObject); - function GetNamedVar( SQLQuery: WideString; x: WideString; - HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideString; - function GetVar( SQLQuery: WideString; x: Integer = 0; - HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideString; - function GetResults( SQLQuery: WideString; - HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet; - function GetCol( SQLQuery: WideString; x: Integer = 0; - HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideStrings.TWideStringList; - procedure ZSQLMonitor1LogTrace(Sender: TObject; Event: TZLoggingEvent); procedure MenuTablelistColumnsClick(Sender: TObject); - procedure CancelQuery; - procedure CheckConnection(); procedure QueryLoad( filename: String; ReplaceContent: Boolean = true ); - procedure ExecuteNonQuery(SQLQuery: String); - function ExecuteQuery(query: String): TDataSet; function CreateOrGetRemoteQueryTab(sender: THandle): THandle; procedure DataGridChange(Sender: TBaseVirtualTree; Node: PVirtualNode); procedure DataGridCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; @@ -623,7 +599,6 @@ type procedure menuExploreClick(Sender: TObject); procedure menuInsertSnippetAtCursorClick(Sender: TObject); procedure menuLoadSnippetClick(Sender: TObject); - procedure RunAsyncPost(ds: TDeferDataSet); procedure vstGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); procedure vstInitNode(Sender: TBaseVirtualTree; ParentNode, Node: @@ -663,7 +638,6 @@ type procedure vstGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: WideString); - procedure ProcessSqlLog; procedure ListCommandStatsBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); @@ -739,13 +713,7 @@ type ReachedEOT : Boolean; FDelimiter: String; ServerUptime : Integer; - time_connected : Cardinal; viewingdata : Boolean; - FMysqlConn : TMysqlConn; - FConn : TOpenConnProf; - QueryRunningInterlock : Integer; - UserQueryFired : Boolean; - UserQueryFiring : Boolean; CachedTableLists : WideStrings.TWideStringList; EditVariableForm : TfrmEditVariable; FileNameSessionLog : String; @@ -754,10 +722,10 @@ type SqlMessagesLock : TRtlCriticalSection; dsShowEngines, dsHaveEngines, - dsCollations : TDataset; - FilterPanelManuallyOpened : Boolean; + dsCollations, FSelectedTableColumns, - FSelectedTableKeys : TDataset; + FSelectedTableKeys : TMySQLQuery; + FilterPanelManuallyOpened : Boolean; DataGridDB, DataGridTable : WideString; PrevTableColWidths : WideStrings.TWideStringList; DataGridHasChanges : Boolean; @@ -770,10 +738,6 @@ type function GetParamValue(const paramChar: Char; const paramName: string; var curIdx: Byte; out paramValue: string): Boolean; procedure SetDelimiter(Value: String); - function GetQueryRunning: Boolean; - procedure SetQueryRunning(running: Boolean); - procedure WaitForQueryCompletion(WaitForm: TfrmQueryProgress; query: TMySqlQuery; ForceDialog: Boolean); - function RunThreadedQuery(AQuery: WideString; ForceDialog: Boolean): TMysqlQuery; procedure DisplayRowCountStats(MatchingRows: Int64 = -1); procedure insertFunction(Sender: TObject); function GetActiveDatabase: WideString; @@ -781,15 +745,17 @@ type procedure SetSelectedDatabase(db: WideString); procedure SetVisibleListColumns( List: TVirtualStringTree; Columns: WideStrings.TWideStringList ); procedure ToggleFilterPanel(ForceVisible: Boolean = False); - function GetSelectedTableColumns: TDataset; - function GetSelectedTableKeys: TDataset; + function GetSelectedTableColumns: TMySQLQuery; + function GetSelectedTableKeys: TMySQLQuery; procedure AutoCalcColWidths(Tree: TVirtualStringTree; PrevLayout: Widestrings.TWideStringlist = nil); procedure PlaceObjectEditor(Which: TListNodeType); procedure SetTabCaption(PageIndex: Integer; Text: String); function ConfirmTabClose(PageIndex: Integer): Boolean; procedure SaveQueryMemo(Filename: String; OnlySelection: Boolean); procedure UpdateFilterPanel(Sender: TObject); + procedure DatabaseChanged(Database: WideString); public + Connection: TMySQLConnection; cancelling: Boolean; virtualDesktopName: string; TableToolsDialog: TfrmTableTools; @@ -804,7 +770,6 @@ type TemporaryDatabase : WideString; dataselected : Boolean; editing : Boolean; - mysql_version : Integer; WindowNumber : Integer; SessionName : String; VTRowDataListVariables, @@ -812,9 +777,6 @@ type VTRowDataListProcesses, VTRowDataListCommandStats, VTRowDataListTables : TVTreeDataArray; - - FProgressForm : TFrmQueryProgress; - // Variables set by preferences dialog prefRememberFilters : Boolean; prefLogsqlnum, @@ -853,31 +815,22 @@ type procedure FillPopupQueryLoad; procedure PopupQueryLoadRemoveAbsentFiles( sender: TObject ); procedure SessionConnect(Sender: TObject); - function InitConnection(parNetType: Integer; parHost, parPort, parUser, parPass, parCompress, parSession: WideString): Boolean; - //procedure HandleQueryNotification(ASender : TMysqlQuery; AEvent : Integer); + function InitConnection(parHost, parSocketname, parPort, parUser, parPass, parCompress, parSession: WideString): Boolean; - function ExecUpdateQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false): Int64; - function ExecSelectQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet; - procedure ExecUseQuery(db: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false); - - property FQueryRunning: Boolean read GetQueryRunning write SetQueryRunning; function ActiveGrid: TVirtualStringTree; function GridResult(Grid: TBaseVirtualTree): TGridResult; overload; function GridResult(PageIndex: Integer): TGridResult; overload; - property MysqlConn : TMysqlConn read FMysqlConn; - property Conn : TOpenConnProf read FConn; property ActiveDatabase : WideString read GetActiveDatabase write SetSelectedDatabase; property SelectedTable : TListNode read GetSelectedTable; - function FetchActiveDbTableList: TDataSet; - function RefreshActiveDbTableList: TDataSet; - function FetchDbTableList(db: WideString): TDataSet; - function RefreshDbTableList(db: WideString): TDataSet; + function FetchActiveDbTableList: TMySQLQuery; + function RefreshActiveDbTableList: TMySQLQuery; + function FetchDbTableList(db: WideString): TMySQLQuery; + function RefreshDbTableList(db: WideString): TMySQLQuery; procedure ClearDbTableList(db: WideString); function DbTableListCachedAndValid(db: WideString): Boolean; procedure ClearAllTableLists; - procedure EnsureDatabase; procedure TestVTreeDataArray( P: PVTreeDataArray ); function GetVTreeDataArray( VT: TBaseVirtualTree ): PVTreeDataArray; procedure ActivateFileLogging; @@ -899,8 +852,8 @@ type function CheckUniqueKeyClause: Boolean; procedure DataGridInsertRow; procedure DataGridCancel(Sender: TObject); - property SelectedTableColumns: TDataset read GetSelectedTableColumns write FSelectedTableColumns; - property SelectedTableKeys: TDataset read GetSelectedTableKeys write FSelectedTableKeys; + property SelectedTableColumns: TMySQLQuery read GetSelectedTableColumns write FSelectedTableColumns; + property SelectedTableKeys: TMySQLQuery read GetSelectedTableKeys write FSelectedTableKeys; procedure CalcNullColors; procedure FillDataViewPopup; procedure GetDataViews(List: TStrings); @@ -909,7 +862,7 @@ type function GetRegKeyTable: String; procedure SaveListSetup( List: TVirtualStringTree ); procedure RestoreListSetup( List: TVirtualStringTree ); - function GetCollations(Items: TWideStrings = nil): TDataset; + function GetCollations(Items: TWideStrings = nil): TMySQLQuery; procedure SetEditorTabCaption(Editor: TFrame; ObjName: WideString); procedure ResetSelectedTableStuff; procedure SetWindowCaption; @@ -952,7 +905,7 @@ const implementation uses - About, loaddata, printlist, copytable, insertfiles, Threading, + About, loaddata, printlist, copytable, insertfiles, mysql_structures, UpdateCheck, uVistaFuncs, runsqlfile, column_selection, data_sorting, grideditlinks, dataviewsave; @@ -960,41 +913,6 @@ uses {$R *.DFM} -procedure TMainForm.HandleWMComplete(var msg: TMessage); -begin - HandleWMCompleteMessage(msg); -end; - -procedure TMainForm.HandleWMCopyData(var msg: TWMCopyData); -begin - HandleWMCopyDataMessage(msg); -end; - -procedure TMainForm.HandleWMProcessLog(var msg: TMessage); -begin - ProcessSqlLog; -end; - -function TMainForm.ExecuteRemoteQuery(sender: THandle; query: string): TDataSet; -//var - //tab: THandle; -begin - // tab := TMDIChild(ActiveMDIChild).CreateOrGetRemoteQueryTab(sender); - // TQueryTab(tab).AddText(query); - // tab.ExecOrQueueQuery(query); - result := ExecuteQuery(query); -end; - -procedure TMainForm.ExecuteRemoteNonQuery(sender: THandle; query: string); -//var - //tab: THandle; -begin - // tab := TMDIChild(ActiveMDIChild).CreateOrGetRemoteQueryTab(sender); - // TQueryTab(tab).AddText(query); - // tab.ExecOrQueueQuery(query); - ExecuteNonQuery(query); -end; - procedure TMainForm.showstatus(msg: string=''; panel: Integer=6); begin // show Message in statusbar @@ -1096,7 +1014,7 @@ var begin flushwhat := UpperCase(TAction(Sender).Caption); delete(flushwhat, pos('&', flushwhat), 1); - ExecUpdateQuery('FLUSH ' + flushwhat); + Connection.Query('FLUSH ' + flushwhat); if Sender = actFlushTableswithreadlock then begin MessageDlg( 'Tables have been flushed and read lock acquired.'#10 + @@ -1104,7 +1022,7 @@ begin 'Press OK to unlock when done...', mtInformation, [mbOk], 0 ); - ExecUpdateQuery('UNLOCK TABLES'); + Connection.Query('UNLOCK TABLES'); end; end; @@ -1213,32 +1131,6 @@ begin end; -var - spareMemory: Pointer = nil; - -procedure HandleRuntimeError(ErrorCode: Byte; ErrorAddr: Pointer); -begin - if spareMemory <> nil then FreeMem(spareMemory); - debug('mem: released spare block.'); - spareMemory := nil; - if MainForm <> nil then begin - PostMessage(MainForm.Handle, WM_REFILL_SPAREBUF, 0, 0); - end; - raise Exception.Create('Runtime error ' + IntToStr(ErrorCode) + ' at ' + IntToHex(Cardinal(ErrorAddr), 8) + '.'); -end; - -procedure SpareBufRefill; -begin - debug('mem: reallocating spare block.'); - if spareMemory = nil then spareMemory := AllocMem(6543210); -end; - -procedure TMainForm.HandleWMRefill(var msg: TMessage); -begin - SpareBufRefill; -end; - - {*** OnCreate Event Important to set the windowstate here instead of in OnShow @@ -1265,9 +1157,6 @@ begin // Use new Vista dialogs per default. //UseLatestCommonDialogs := True; - SpareBufRefill; - ErrorProc := HandleRuntimeError; - refreshMonitorConfig; loadWindowConfig; @@ -1296,9 +1185,6 @@ begin // Folder for session logfiles DirnameSessionLogs := DirnameUserAppData + 'Sessionlogs\'; - QueryRunningInterlock := 0; - UserQueryFired := False; - UserQueryFiring := False; TemporaryDatabase := ''; // SQLFiles-History @@ -1473,9 +1359,9 @@ end; } procedure TMainForm.Startup; var - curParam, parNetType: Byte; + curParam, NetType: Byte; sValue, - parHost, parPort, parUser, parPass, + parHost, parSocketname, parPort, parUser, parPass, parCompress, parSession: String; LastUpdatecheck, LastStatsCall, LastConnect: TDateTime; UpdatecheckInterval, i: Integer; @@ -1556,7 +1442,6 @@ begin // Check commandline if parameters were passed. Otherwise show connections windows curParam := 1; - parNetType := NETTYPE_TCPIP; while curParam <= ParamCount do begin // -M and -d are choosen not to conflict with mysql.exe // http://dev.mysql.com/doc/refman/5.0/en/mysql-command-options.html @@ -1567,8 +1452,8 @@ begin parHost := sValue else if GetParamValue('P', 'port', curParam, sValue) then parPort := sValue - else if GetParamValue('T', 'nettype', curParam, sValue) then - parNetType := StrToIntDef(sValue, NETTYPE_TCPIP) + else if GetParamValue('s', 'socket', curParam, sValue) then + parSocketname := sValue else if GetParamValue('C', 'compress', curParam, sValue) then parCompress := sValue else if GetParamValue('u', 'user', curParam, sValue) then @@ -1582,8 +1467,14 @@ begin // Find stored session if -dSessionName was passed if (parSession <> '') and MainReg.KeyExists(REGPATH + REGKEY_SESSIONS + parSession) then begin - parNetType := GetRegValue(REGNAME_NETTYPE, DEFAULT_NETTYPE, parSession); + NetType := GetRegValue(REGNAME_NETTYPE, NETTYPE_TCPIP, parSession); parHost := GetRegValue(REGNAME_HOST, DEFAULT_HOST, parSession); + if NetType = NETTYPE_TCPIP then + parSocketname := '' + else begin + parSocketname := parHost; + parHost := '.'; + end; parUser := GetRegValue(REGNAME_USER, DEFAULT_USER, parSession); parPass := decrypt(GetRegValue(REGNAME_PASSWORD, DEFAULT_PASSWORD, parSession)); parPort := GetRegValue(REGNAME_PORT, IntToStr(DEFAULT_PORT), parSession); @@ -1595,7 +1486,7 @@ begin if CommandLineMode then begin if parSession = '' then parSession := parHost; - Connected := InitConnection(parNetType, parHost, parPort, parUser, parPass, parCompress, parSession); + Connected := InitConnection(parHost, parSocketname, parPort, parUser, parPass, parCompress, parSession); end; // Auto connection via preference setting @@ -1603,9 +1494,17 @@ begin if (not CommandLineMode) and (not Connected) and GetRegValue(REGNAME_AUTORECONNECT, DEFAULT_AUTORECONNECT) then begin LastSession := GetRegValue(REGNAME_LASTSESSION, ''); if LastSession <> '' then begin + parHost := GetRegValue(REGNAME_HOST, '', LastSession); + NetType := GetRegValue(REGNAME_NETTYPE, NETTYPE_TCPIP, LastSession); + if NetType = NETTYPE_TCPIP then + parSocketname := '' + else begin + parSocketname := parHost; + parHost := '.'; + end; Connected := InitConnection( - GetRegValue(REGNAME_NETTYPE, DEFAULT_NETTYPE, LastSession), - GetRegValue(REGNAME_HOST, '', LastSession), + parHost, + parSocketname, GetRegValue(REGNAME_PORT, '', LastSession), GetRegValue(REGNAME_USER, '', LastSession), decrypt(GetRegValue(REGNAME_PASSWORD, '', LastSession)), @@ -1648,9 +1547,6 @@ procedure TMainForm.DoAfterConnect; var i, j: Integer; lastUsedDB: WideString; - v: String[50]; - v1, v2, v3: String; - rx: TRegExpr; functioncats : TStringList; miGroup, miFilterGroup, @@ -1663,28 +1559,12 @@ begin if GetRegValue(REGNAME_LOGTOFILE, DEFAULT_LOGTOFILE) then ActivateFileLogging; - TimerConnected.Enabled := true; - LogSQL('Connected. Thread-ID: ' + IntToStr( MySQLConn.Connection.GetThreadId )); - - // Detect server version - // Be careful with version suffixes, for example: '4.0.31-20070605_Debian-5-log' - v := GetVar( 'SELECT VERSION()' ); - rx := TRegExpr.Create; - rx.ModifierG := True; - rx.Expression := '^(\d+)\.(\d+)\.(\d+)'; - if rx.Exec(v) then begin - v1 := rx.Match[1]; - v2 := rx.Match[2]; - v3 := rx.Match[3]; - end; - rx.Free; - mysql_version := MakeInt(v1) *10000 + MakeInt(v2) *100 + MakeInt(v3); - tabHost.Caption := 'Host: '+MySQLConn.Connection.HostName; - showstatus('MySQL '+v1+'.'+v2+'.'+v3, 3); + tabHost.Caption := 'Host: '+Connection.HostName; + showstatus('MySQL '+Connection.ServerVersionStr, 3); // Save server version OpenRegistry(SessionName); - Mainreg.WriteInteger(REGNAME_SERVERVERSION, mysql_version); + Mainreg.WriteInteger(REGNAME_SERVERVERSION, Connection.ServerVersionInt); Mainreg.WriteString(REGNAME_LASTCONNECT, DateTimeToStr(Now)); comboOnlyDBs.Items.Text := Utf8Decode(GetRegValue(REGNAME_ONLYDBS, '', SessionName)); @@ -1698,10 +1578,6 @@ begin CheckUptime; - // Define window properties - SetWindowConnected( true ); - WindowNumber := SetWindowName(SessionName); - // Reselect last used database if GetRegValue( REGNAME_RESTORELASTUSEDDB, DEFAULT_RESTORELASTUSEDDB ) then begin lastUsedDB := Utf8Decode(GetRegValue(REGNAME_LASTUSEDDB, '', SessionName)); @@ -1752,14 +1628,14 @@ begin miFunction.Caption := '&-'; miFunction.Hint := MySqlFunctions[j].Name + MySqlFunctions[j].Declaration; // Take care of needed server version - if MySqlFunctions[j].Version <= mysql_version then begin + if MySqlFunctions[j].Version <= Connection.ServerVersionInt then begin if MySqlFunctions[j].Description <> '' then miFunction.Hint := miFunction.Hint + ' - ' + Copy(MySqlFunctions[j].Description, 0, 200 ); miFunction.Tag := j; // Place menuitem on menu miFunction.OnClick := insertFunction; end else begin - miFunction.Hint := miFunction.Hint + ' - ('+STR_NOTSUPPORTED+', needs >= '+ConvertServerVersion(MySqlFunctions[j].Version)+')'; + miFunction.Hint := miFunction.Hint + ' - ('+STR_NOTSUPPORTED+', needs >= '+Connection.ConvertServerVersion(MySqlFunctions[j].Version)+')'; miFunction.Enabled := False; end; // Prevent generating a seperator for ShortHint and LongHint @@ -1806,11 +1682,8 @@ begin FreeAndNil(CreateDatabaseForm); // Closing connection - if Assigned(FMysqlConn) then begin - LogSQL('Closing connection to "'+SessionName+'" session (' + FMysqlConn.Connection.hostname + ') ...'); - FMysqlConn.Disconnect; - FreeAndNil(FMysqlConn); - end; + if Assigned(Connection) then + FreeAndNil(Connection); if prefLogToFile then DeactivateFileLogging; @@ -1821,12 +1694,8 @@ begin ListProcesses.Tag := VTREE_NOTLOADED; ListCommandstats.Tag := VTREE_NOTLOADED; - SetWindowConnected( false ); - SetWindowName( main.discname ); Application.Title := APPNAME; - TimerConnected.Enabled := False; - time_connected := 0; TimerHostUptime.Enabled := False; end; @@ -1902,43 +1771,6 @@ begin UserManagerForm.ShowModal; end; -procedure TMainForm.menuWindowClick(Sender: TObject); -var - i: integer; - list: TWindowDataArray; - item: TMenuItem; -begin - // Delete dynamically added connection menu items. - // NOTE: The menu doesn't like having 0 items, so we keep one which we delete later. - for i := menuWindow.Count - 1 downto 1 do menuWindow.Delete(i); - - // Check if all the heidisql windows are still alive. - CheckForCrashedWindows; - - // Fetch the list of windows. - list := GetWindowList; - - // TODO: Load "all" array with all connections - - // Re-create dynamic menu items. - for i := 0 to High(list) do with list[i] do begin - // TODO: Remove connection with this UID from "all" array - item := TMenuItem.Create(self); - if namePostfix <> 0 then name := name + Format(' (%d)', [namePostFix]); - item.Caption := name; - if (appHandle = Handle) and (connected) then item.ImageIndex := ICON_MYSELF_CONNECTED - else if (appHandle = Handle) and (not connected) then item.ImageIndex := ICON_MYSELF_DISCONNECTED - else if (appHandle <> Handle) and (connected) then item.ImageIndex := ICON_OTHER_CONNECTED - else if (appHandle <> Handle) and (not connected) then item.ImageIndex := ICON_OTHER_DISCONNECTED; - item.Tag := appHandle; - item.OnClick := focusWindow; - menuWindow.Add(item); - end; - // NOTE: The menu breaks if it has 0 items at any point. Therefore we delete item 0 as the last thing. - // Perhaps later the Window menu will contain more items, for now it's initially filled with a fake menu item. - menuWindow.Delete(0); -end; - procedure TMainForm.actAboutBoxExecute(Sender: TObject); begin // Info-Box @@ -2030,7 +1862,6 @@ end; procedure TMainForm.menuConnectionsPopup(Sender: TObject); var i: integer; - list: TWindowDataArray; item: TMenuItem; Connections: TStringList; begin @@ -2039,32 +1870,6 @@ begin menuConnections.Items.Delete(i); end; - // Check if all the heidisql windows are still alive. - CheckForCrashedWindows; - - // Fetch list of heidisql windows. - list := GetWindowList; - - // Re-create dynamic menu items. - for i := 0 to High(list) do with list[i] do begin - // TODO: Remove connection with this UID from "all" array - item := TMenuItem.Create(self); - if namePostfix <> 0 then name := name + Format(' (%d)', [namePostFix]); - item.Caption := name; - if (appHandle = Handle) and (connected) then item.ImageIndex := ICON_MYSELF_CONNECTED - else if (appHandle = Handle) and (not connected) then item.ImageIndex := ICON_MYSELF_DISCONNECTED - else if (appHandle <> Handle) and (connected) then item.ImageIndex := ICON_OTHER_CONNECTED - else if (appHandle <> Handle) and (not connected) then item.ImageIndex := ICON_OTHER_DISCONNECTED; - item.Tag := appHandle; - item.OnClick := focusWindow; - menuConnections.Items.Add(item); - end; - - // Add separator - item := TMenuItem.Create(menuConnections); - item.Caption := '-'; - menuConnections.Items.Add(item); - // "Session manager" and "New window" items item := TMenuItem.Create(menuConnections); item.Action := actSessionManager; @@ -2136,7 +1941,7 @@ end; // Escape database, table, field, index or key name. function TMainform.mask(str: WideString) : WideString; begin - result := maskSql(mysql_version, str); + result := Connection.QuoteIdent(str); end; @@ -2372,7 +2177,7 @@ end; procedure TMainForm.actExportTablesExecute(Sender: TObject); var - ds: TDataset; + Results: TMySQLQuery; InDBTree: Boolean; Comp: TComponent; begin @@ -2388,10 +2193,10 @@ begin if SelectedTable.Text <> '' then ExportSQLForm.SelectedTables.Add(SelectedTable.Text) else if Mainform.ActiveDatabase <> '' then begin - ds := Mainform.FetchDbTableList(ActiveDatabase); - while not ds.Eof do begin - ExportSQLForm.SelectedTables.Add(ds.FieldByName(DBO_NAME).AsWideString); - ds.Next; + Results := Mainform.FetchDbTableList(ActiveDatabase); + while not Results.Eof do begin + ExportSQLForm.SelectedTables.Add(Results.Col(DBO_NAME)); + Results.Next; end; end; end else @@ -2421,12 +2226,12 @@ var if (i > 0) and MultiDrops then sql := sql + ', '; sql := sql + mask(List[i]); if not MultiDrops then begin - ExecUpdateQuery(baseSql + sql); + Connection.Query(baseSql + sql); sql := ''; end; end; if MultiDrops then - ExecUpdateQuery(baseSql + sql); + Connection.Query(baseSql + sql); end; FreeAndNil(List); end; @@ -2453,7 +2258,7 @@ begin Abort; Screen.Cursor := crHourglass; try - ExecUpdateQuery( 'DROP DATABASE ' + mask(activeDB) ); + Connection.Query('DROP DATABASE ' + mask(activeDB)); ClearDbTableList(activeDB); DBtree.Selected[DBtree.GetFirst] := true; RefreshTree(False); @@ -2564,17 +2369,23 @@ end; procedure TMainForm.SessionConnect(Sender: TObject); var Session: String; - parNetType: Integer; - parHost, parPort, parUser, parPass, parCompress: WideString; + NetType: Integer; + parHost, parSocketname, parPort, parUser, parPass, parCompress: WideString; begin Session := (Sender as TMenuItem).Caption; - parNetType := GetRegValue(REGNAME_NETTYPE, DEFAULT_NETTYPE, Session); + NetType := GetRegValue(REGNAME_NETTYPE, NETTYPE_TCPIP, Session); parHost := GetRegValue(REGNAME_HOST, '', Session); + if NetType = NETTYPE_TCPIP then + parSocketname := '' + else begin + parSocketname := parHost; + parHost := '.'; + end; parUser := GetRegValue(REGNAME_USER, '', Session); parPass := decrypt(GetRegValue(REGNAME_PASSWORD, '', Session)); parPort := GetRegValue(REGNAME_PORT, '', Session); parCompress := IntToStr(Integer(GetRegValue(REGNAME_COMPRESSED, DEFAULT_COMPRESSED, Session))); - if InitConnection(parNetType, parHost, parPort, parUser, parPass, parCompress, Session) then + if InitConnection(parHost, parSocketname, parPort, parUser, parPass, parCompress, Session) then DoAfterConnect; end; @@ -2583,66 +2394,43 @@ end; Receive connection parameters and create the mdi-window Paremeters are either sent by connection-form or by commandline. } -function TMainform.InitConnection(parNetType: Integer; parHost, parPort, parUser, parPass, parCompress, parSession: WideString): Boolean; +function TMainform.InitConnection(parHost, parSocketname, parPort, parUser, parPass, parCompress, parSession: WideString): Boolean; var - MysqlConnection: TMysqlConn; - Profile: TOpenConnProf; - UsingPass, NetType: String; + ConnectionAttempt: TMySQLConnection; SessionExists: Boolean; begin - // fill structure - ZeroMemory(@Profile, SizeOf(Profile)); - Profile.MysqlParams.Protocol := 'mysql'; - Profile.MysqlParams.NetType := parNetType; - Profile.MysqlParams.Host := Trim( parHost ); - Profile.MysqlParams.Port := StrToIntDef(parPort, DEFAULT_PORT); - Profile.MysqlParams.Database := ''; - Profile.MysqlParams.User := parUser; - Profile.MysqlParams.Pass := parPass; - if Integer(parCompress) > 0 then - Profile.MysqlParams.PrpCompress := 'true' - else - Profile.MysqlParams.PrpCompress := 'false'; - Profile.MysqlParams.PrpDbless := 'true'; - Profile.MysqlParams.PrpClientLocalFiles := 'true'; - Profile.MysqlParams.PrpClientInteractive := 'true'; - - MysqlConnection := TMysqlConn.Create(@Profile); + ConnectionAttempt := TMySQLConnection.Create(Self); + ConnectionAttempt.OnLog := LogSQL; + ConnectionAttempt.OnDatabaseChanged := DatabaseChanged; + ConnectionAttempt.Hostname := parHost; + ConnectionAttempt.Socketname := parSocketname; + ConnectionAttempt.Username := parUser; + ConnectionAttempt.Password := parPass; + ConnectionAttempt.Port := StrToIntDef(parPort, MYSQL_PORT); + ConnectionAttempt.Active := True; // attempt to establish connection - if Profile.MysqlParams.Pass <> '' then UsingPass := 'Yes' else UsingPass := 'No'; - case parNetType of - NETTYPE_TCPIP: NetType := 'TCP/IP'; - NETTYPE_NAMEDPIPE: NetType := 'named pipe'; - else NetType := 'unknown protocol'; - end; - LogSQL('Connecting to '+Profile.MysqlParams.Host+' via '+NetType+ - ', username '+Profile.MysqlParams.User+ - ', using password: '+UsingPass+' ...'); SessionExists := MainReg.KeyExists(REGPATH + REGKEY_SESSIONS + parSession); - if MysqlConnection.Connect <> MCR_SUCCESS then begin + if not ConnectionAttempt.Active then begin // attempt failed if SessionExists then begin // Save "refused" counter OpenRegistry(parSession); MainReg.WriteInteger(REGNAME_REFUSEDCOUNT, GetRegValue(REGNAME_REFUSEDCOUNT, 0, parSession)+1); end; - MessageDlg ( 'Could not establish connection! Details:'+CRLF+CRLF+MysqlConnection.LastError, mtError, [mbOK], 0); Result := False; - FreeAndNil(MysqlConnection); + FreeAndNil(ConnectionAttempt); end else begin if SessionExists then begin // Save "refused" counter OpenRegistry(parSession); MainReg.WriteInteger(REGNAME_CONNECTCOUNT, GetRegValue(REGNAME_CONNECTCOUNT, 0, parSession)+1); end; + Result := True; - Profile.MysqlConn := MysqlConnection.Connection; - if Assigned(FMysqlConn) then + if Assigned(Connection) then DoDisconnect; - // Assign global connection objects - FConn := Profile; - FMysqlConn := MysqlConnection; + Connection := ConnectionAttempt; SessionName := parSession; end; ShowStatus( STATUS_MSG_READY ); @@ -2717,13 +2505,13 @@ begin @see http://dev.mysql.com/doc/refman/5.0/en/truncate.html @see https://sourceforge.net/tracker/index.php?func=detail&aid=1644143&group_id=164593&atid=832350 } - if mysql_version < 50003 then + if Connection.ServerVersionInt < 50003 then sql_pattern := 'DELETE FROM ' else sql_pattern := 'TRUNCATE '; for i:=0 to t.count-1 do - ExecUpdateQuery( sql_pattern + mask(t[i]) ); + Connection.Query( sql_pattern + mask(t[i]) ); t.Free; actRefresh.Execute; Screen.Cursor := crDefault; @@ -2793,10 +2581,10 @@ end; procedure TMainForm.actSQLhelpExecute(Sender: TObject); var keyword : String; - ds: TDataset; + Results: TMySQLQuery; begin // Call SQL Help from various places - if mysql_version < 40100 then + if Connection.ServerVersionInt < 40100 then exit; keyword := ''; @@ -2806,9 +2594,9 @@ begin // Data-Tab else if (PageControlMain.ActivePage = tabData) and Assigned(DataGrid.FocusedNode) then begin - ds := SelectedTableColumns; - ds.RecNo := DataGrid.FocusedColumn; - keyword := ds.FieldByName('Type').AsWideString; + Results := SelectedTableColumns; + Results.RecNo := DataGrid.FocusedColumn; + keyword := Results.Col('Type'); end else if QueryTabActive and ActiveQueryHelpers.Focused then begin @@ -3298,105 +3086,44 @@ begin end; -function TMainForm.GetQueryRunning: Boolean; -begin - Result := ( QueryRunningInterlock = 1 ); -end; - - -procedure TMainForm.SetQueryRunning(running: Boolean); -var - newValue : Integer; - oldValue : Integer; -begin - if ( running ) then - begin - newValue := 1; - end - else - begin - newValue := 0; - end; - - oldValue := InterlockedExchange( QueryRunningInterlock, newValue ); - if ( newValue = oldValue ) then - begin - case ( newValue ) of - 1 : - begin - raise Exception.Create( 'Error: Default connection is ' + - 'already executing a query.' ); - end; - 0 : - begin - raise Exception.Create( 'Internal badness: Double reset of running ' + - 'flag.' ); - end; - end; - end; -end; - - {** Add a SQL-command or comment to SynMemoSQLLog } -procedure TMainForm.LogSQL(msg: WideString = ''; comment: Boolean = true); +procedure TMainForm.LogSQL(Msg: WideString; Category: TMySQLLogCategory=lcInfo); var - snip : boolean; + snip, IsSQL: Boolean; begin + if Category = lcDebug then + Exit; // Shorten very long messages - snip := (prefLogSqlWidth > 0) and (Length(msg) > prefLogSqlWidth); + snip := (prefLogSqlWidth > 0) and (Length(Msg) > prefLogSqlWidth); + IsSQL := Category = lcSQL; if snip then begin - msg := - Copy( msg, 0, prefLogSqlWidth ) + + Msg := + Copy(Msg, 0, prefLogSqlWidth) + '/* large SQL query, snipped at ' + - FormatNumber( prefLogSqlWidth ) + + FormatNumber(prefLogSqlWidth) + ' characters */'; - end else if (not snip) and (not comment) then - msg := msg + Delimiter - else if comment then - msg := '/* ' + msg + ' */'; - - msg := WideStringReplace( msg, #9, ' ', [rfReplaceAll] ); - msg := WideStringReplace( msg, #10, ' ', [rfReplaceAll] ); - msg := WideStringReplace( msg, #13, ' ', [rfReplaceAll] ); - msg := WideStringReplace( msg, ' ', ' ', [rfReplaceAll] ); - - EnterCriticalSection(SqlMessagesLock); - try - SqlMessages.Add(msg); - finally - LeaveCriticalSection(SqlMessagesLock); - end; - PostMessage(Handle, WM_PROCESSLOG, 0, 0); -end; - -procedure TMainForm.ProcessSqlLog; -var - msg: WideString; -begin - EnterCriticalSection(SqlMessagesLock); - try - if SqlMessages = nil then Exit; - if SqlMessages.Count < 1 then Exit; - msg := SqlMessages[0]; - SqlMessages.Delete(0); - finally - LeaveCriticalSection(SqlMessagesLock); - end; - - SynMemoSQLLog.Lines.Add( msg ); + end else if (not snip) and IsSQL then + Msg := Msg + Delimiter + else if not IsSQL then + Msg := '/* ' + Msg + ' */'; + Msg := WideStringReplace(Msg, #9, ' ', [rfReplaceAll]); + Msg := WideStringReplace(Msg, #10, ' ', [rfReplaceAll]); + Msg := WideStringReplace(Msg, #13, ' ', [rfReplaceAll]); + Msg := WideStringReplace(Msg, ' ', ' ', [rfReplaceAll]); + SynMemoSQLLog.Lines.Add(Msg); TrimSQLLog; // Scroll to last line and repaint - SynMemoSQLLog.GotoLineAndCenter( SynMemoSQLLog.Lines.Count ); + SynMemoSQLLog.GotoLineAndCenter(SynMemoSQLLog.Lines.Count); SynMemoSQLLog.Repaint; // Log to file? if prefLogToFile then try - WriteLn( FileHandleSessionLog, Format('[%s] %s', [DateTimeToStr(Now), msg]) ); + WriteLn(FileHandleSessionLog, Format('[%s] %s', [DateTimeToStr(Now), msg])); except DeactivateFileLogging; MessageDlg('Error writing to session log file:'+CRLF+FileNameSessionLog+CRLF+CRLF+'Logging is disabled now.', mtError, [mbOK], 0); @@ -3552,8 +3279,8 @@ begin SelectedTableKeys.First; for k := 0 to SelectedTableKeys.RecordCount - 1 do begin - if (SelectedTableKeys.FieldByName('Key_name').AsString = 'PRIMARY') - and (SelectedTableKeys.FieldByName('Column_name').AsWideString = name) then begin + if (SelectedTableKeys.Col('Key_name') = 'PRIMARY') + and (SelectedTableKeys.Col('Column_name') = name) then begin DataGridResult.Columns[idx].IsPriPart := True; break; end; @@ -3646,7 +3373,7 @@ begin ColExists := False; SelectedTableColumns.First; while not SelectedTableColumns.Eof do begin - if FDataGridSelect[i] = SelectedTableColumns.FieldByName('Field').AsWideString then begin + if FDataGridSelect[i] = SelectedTableColumns.Col('Field') then begin ColExists := True; break; end; @@ -3665,10 +3392,10 @@ begin debug('mem: initializing browse columns.'); SelectedTableColumns.First; while not SelectedTableColumns.Eof do begin - ColName := SelectedTableColumns.FieldByName('Field').AsWideString; + ColName := SelectedTableColumns.Col('Field'); ShowIt := (FDataGridSelect.Count=0) or (FDataGridSelect.IndexOf(ColName)>-1); if ShowIt or (KeyCols.IndexOf(ColName)>-1) then begin - ColType := SelectedTableColumns.FieldByName('Type').AsString; + ColType := SelectedTableColumns.Col('Type'); rx.Expression := '^((tiny|medium|long)?(text|blob)|(var)?(char|binary))\b(\(\d+\))?'; if rx.Exec(ColType) then begin select_base := select_base + ' ' + 'LEFT(' + Mask(ColName) + ', ' + IntToStr(GridMaxData) + ')' + ','; @@ -3676,7 +3403,7 @@ begin select_base := select_base + ' ' + Mask(ColName) + ','; end; select_base_full := select_base_full + ' ' + Mask(ColName) + ','; - InitColumn(ColName, SelectedTableColumns.FieldByName('Type').AsString, ShowIt); + InitColumn(ColName, SelectedTableColumns.Col('Type'), ShowIt); end; SelectedTableColumns.Next; end; @@ -3753,8 +3480,7 @@ procedure TMainForm.DisplayRowCountStats(MatchingRows: Int64); var rows_total : Int64; // total rowcount IsFiltered, IsInnodb: Boolean; - ds: TDataSet; - i: Integer; + Results: TMySQLQuery; s: WideString; begin lblDataTop.Caption := ActiveDatabase + '.' + SelectedTable.Text; @@ -3762,14 +3488,14 @@ begin IsFiltered := self.DataGridCurrentFilter <> ''; if GetFocusedTreeNodeType = lntTable then begin // Get rowcount from table - ds := FetchActiveDbTableList; + Results := FetchActiveDbTableList; rows_total := -1; IsInnodb := False; - for i := 0 to ds.RecordCount - 1 do begin - if ds.FieldByName(DBO_NAME).AsWideString = SelectedTable.Text then begin - s := ds.FieldByName(DBO_ROWS).AsString; + while not Results.Eof do begin + if Results.Col(DBO_NAME) = SelectedTable.Text then begin + s := Results.Col(DBO_ROWS); if s <> '' then rows_total := MakeInt(s); - IsInnodb := ds.Fields[1].AsString = 'InnoDB'; + IsInnodb := Results.Col(1) = 'InnoDB'; break; end; end; @@ -3792,30 +3518,6 @@ begin end; -procedure TMainForm.WaitForQueryCompletion(WaitForm: TfrmQueryProgress; query: TMySqlQuery; ForceDialog: Boolean); -var - signal: Cardinal; -begin - debug( 'Waiting for query to complete.' ); - cancelling := false; - if ForceDialog then begin - debug( 'Showing progress form.' ); - WaitForm.ShowModal(); - end else begin - signal := WaitForSingleObject(query.EventHandle, QueryWaitTime); - if signal = 0 then debug( 'Query completed within ' + IntToStr(QueryWaitTime) + 'msec.' ) - else begin - debug( IntToStr(QueryWaitTime) + 'msec passed, showing progress form.' ); - // Hack: Prevent dynamic loading of records in the context of the wait form's message loop. - DataGrid.Visible := False; - WaitForm.ShowModal(); - end; - end; - CloseHandle(query.EventHandle); - debug( 'Query complete.' ); -end; - - {*** Occurs when active tab has changed. } @@ -3866,39 +3568,19 @@ begin end; -{*** - Ensures that we're connected to the currently selected database. -} -procedure TMainForm.EnsureDatabase; -var - db: WideString; -begin - // Some functions temporarily switch away from the database selected by the user, handle that. - if TemporaryDatabase <> '' then db := TemporaryDatabase - else db := ActiveDatabase; - // Blank = database undefined - if db = '' then Exit; - if (FMysqlConn.Connection.Database <> db) or (UserQueryFired and not UserQueryFiring) then begin - ExecUseQuery(db, false, false); - UserQueryFired := false; - FMysqlConn.Connection.Database := db; - end; -end; - - {*** Look for list of tables for current database in cache. Retrieve from server if necessary. - @return TDataSet The cached list of tables for the active database. + @return TMySQLQuery The cached list of tables for the active database. } -function TMainForm.FetchActiveDbTableList: TDataSet; +function TMainForm.FetchActiveDbTableList: TMySQLQuery; begin Result := FetchDbTableList(ActiveDatabase); end; -function TMainForm.FetchDbTableList(db: WideString): TDataSet; +function TMainForm.FetchDbTableList(db: WideString): TMySQLQuery; var - ds: TDataSet; + Results: TMySQLQuery; OldCursor: TCursor; Unions: TWideStringlist; ListObjectsSQL: WideString; @@ -3910,7 +3592,7 @@ begin ShowStatus('Fetching tables from "' + db + '" ...'); try if not Assigned(InformationSchemaTables) then - InformationSchemaTables := GetCol('SHOW TABLES FROM '+mask(DBNAME_INFORMATION_SCHEMA), 0, True, False); + InformationSchemaTables := Connection.GetCol('SHOW TABLES FROM '+mask(DBNAME_INFORMATION_SCHEMA)); if InformationSchemaTables.IndexOf('TABLES') > -1 then begin Unions := TWideStringlist.Create; @@ -3971,13 +3653,13 @@ begin // For servers lacking the INFORMATION_SCHEMA or the TABLES table ListObjectsSQL := 'SHOW TABLE STATUS FROM ' + mask(db); end; - ds := GetResults(ListObjectsSQL); - CachedTableLists.AddObject(db, ds); + Results := Connection.GetResults(ListObjectsSQL); + CachedTableLists.AddObject(db, Results); // Add table names to SQL highlighter SynSQLSyn1.TableNames.BeginUpdate; - while not ds.Eof do begin - SynSQLSyn1.TableNames.Add(ds.FieldByName(DBO_NAME).AsWideString); - ds.Next; + while not Results.Eof do begin + SynSQLSyn1.TableNames.Add(Results.Col(DBO_NAME)); + Results.Next; end; SynSQLSyn1.TableNames.EndUpdate; finally @@ -3985,21 +3667,21 @@ begin Screen.Cursor := OldCursor; end; end; - Result := TDataSet(CachedTableLists.Objects[CachedTableLists.IndexOf(db)]); + Result := TMySQLQuery(CachedTableLists.Objects[CachedTableLists.IndexOf(db)]); Result.First; end; {*** Nukes cached table list for active database, then refreshes it. - @return TDataSet The newly cached list of tables for the active database. + @return TMySQLQuery The newly cached list of tables for the active database. } -function TMainForm.RefreshActiveDbTableList: TDataSet; +function TMainForm.RefreshActiveDbTableList: TMySQLQuery; begin Result := RefreshDbTableList(ActiveDatabase); end; -function TMainForm.RefreshDbTableList(db: WideString): TDataSet; +function TMainForm.RefreshDbTableList(db: WideString): TMySQLQuery; begin ClearDbTableList(db); Result := FetchDbTableList(db); @@ -4025,12 +3707,11 @@ end; procedure TMainForm.ClearAllTableLists; var idx: Integer; - ds: TDataSet; + Results: TMySQLQuery; begin for idx := 0 to CachedTableLists.Count - 1 do begin - ds := TDataSet(CachedTableLists.Objects[idx]); - ds.Close; - FreeAndNil(ds); + Results := TMySQLQuery(CachedTableLists.Objects[idx]); + FreeAndNil(Results); end; CachedTableLists.Clear; end; @@ -4040,9 +3721,9 @@ procedure TMainForm.LoadDatabaseProperties(db: WideString); var i, img : Integer; bytes : Int64; - ds : TDataSet; + Results : TMySQLQuery; Cap, - SelectedCaptions: WideStrings.TWideStringList; + SelectedCaptions: TWideStringList; begin // DB-Properties Screen.Cursor := crHourGlass; @@ -4050,59 +3731,59 @@ begin // Remember selected nodes SelectedCaptions := GetVTCaptions(ListTables, True); - ds := FetchDbTableList(db); + Results := FetchDbTableList(db); ShowStatus( 'Displaying tables from "' + db + '" ...' ); ListTables.BeginUpdate; ListTables.Clear; - SetLength(VTRowDataListTables, ds.RecordCount); - for i := 1 to ds.RecordCount do - begin - VTRowDataListTables[i-1].Captions := WideStrings.TWideStringList.Create; - Cap := VTRowDataListTables[i-1].Captions; + SetLength(VTRowDataListTables, Results.RecordCount); + i := 0; + while not Results.Eof do begin + VTRowDataListTables[i].Captions := TWideStringList.Create; + Cap := VTRowDataListTables[i].Captions; // Object name - Cap.Add( FieldContent(ds, DBO_NAME) ); - if (FieldContent(ds, DBO_ROWS) <> '') then - Cap.Add( FormatNumber( FieldContent(ds, DBO_ROWS) ) ) + Cap.Add(Results.Col(DBO_NAME)); + if Results.Col(DBO_ROWS, True) <> '' then + Cap.Add( FormatNumber(Results.Col(DBO_ROWS) ) ) else Cap.Add(''); // Size: Data_length + Index_length - bytes := GetTableSize(ds); + bytes := GetTableSize(Results); if bytes >= 0 then Cap.Add(FormatByteNumber(bytes)) else Cap.Add(''); - Cap.Add( FieldContent(ds, DBO_CREATED) ); - Cap.Add( FieldContent(ds, DBO_UPDATED) ); - Cap.Add( FieldContent(ds, DBO_ENGINE) ); - Cap.Add( FieldContent(ds, DBO_COMMENT) ); - Cap.Add( FieldContent(ds, DBO_VERSION) ); - Cap.Add( FieldContent(ds, DBO_ROWFORMAT) ); - if (FieldContent(ds, DBO_AVGROWLEN) <> '') then - Cap.Add( FormatByteNumber(FieldContent(ds, DBO_AVGROWLEN)) ) + Cap.Add(Results.Col(DBO_CREATED, True)); + Cap.Add(Results.Col(DBO_UPDATED, True)); + Cap.Add(Results.Col(DBO_ENGINE, True)); + Cap.Add(Results.Col(DBO_COMMENT, True)); + Cap.Add(Results.Col(DBO_VERSION, True)); + Cap.Add(Results.Col(DBO_ROWFORMAT, True)); + if Results.Col(DBO_AVGROWLEN, True) <> '' then + Cap.Add( FormatByteNumber(Results.Col(DBO_AVGROWLEN)) ) else Cap.Add(''); - if (FieldContent(ds, DBO_MAXDATALEN) <> '') then - Cap.Add( FormatByteNumber(FieldContent(ds, DBO_MAXDATALEN)) ) + if Results.Col(DBO_MAXDATALEN, True) <> '' then + Cap.Add( FormatByteNumber(Results.Col(DBO_MAXDATALEN)) ) else Cap.Add(''); - if (FieldContent(ds, DBO_INDEXLEN) <> '') then - Cap.Add( FormatByteNumber(FieldContent(ds, DBO_INDEXLEN)) ) + if Results.Col(DBO_INDEXLEN, True) <> '' then + Cap.Add( FormatByteNumber(Results.Col(DBO_INDEXLEN, True)) ) else Cap.Add(''); - if (FieldContent(ds, DBO_DATAFREE) <> '') then - Cap.Add( FormatByteNumber(FieldContent(ds, DBO_DATAFREE)) ) + if Results.Col(DBO_DATAFREE, True) <> '' then + Cap.Add( FormatByteNumber(Results.Col(DBO_DATAFREE)) ) else Cap.Add(''); - if (FieldContent(ds, DBO_AUTOINC) <> '') then - Cap.Add( FormatNumber(FieldContent(ds, DBO_AUTOINC)) ) + if Results.Col(DBO_AUTOINC, True) <> '' then + Cap.Add( FormatNumber(Results.Col(DBO_AUTOINC)) ) else Cap.Add(''); - Cap.Add( FieldContent(ds, DBO_AUTOINC) ); - Cap.Add( FieldContent(ds, DBO_COLLATION) ); - Cap.Add( FieldContent(ds, DBO_CHECKSUM) ); - Cap.Add( FieldContent(ds, DBO_CROPTIONS) ); - if ds.FindField(DBO_TYPE) <> nil then - Cap.Add(FieldContent(ds, DBO_TYPE)) + Cap.Add(Results.Col(DBO_AUTOINC)); + Cap.Add(Results.Col(DBO_COLLATION)); + Cap.Add(Results.Col(DBO_CHECKSUM)); + Cap.Add(Results.Col(DBO_CROPTIONS)); + if Results.ColExists(DBO_TYPE) then + Cap.Add(Results.Col(DBO_TYPE)) else Cap.Add('BASE TABLE'); - VTRowDataListTables[i-1].NodeType := GetDBObjectType( ds.Fields); + VTRowDataListTables[i].NodeType := GetDBObjectType(Results); // Find icon - case VTRowDataListTables[i-1].NodeType of + case VTRowDataListTables[i].NodeType of lntTable: img := ICONINDEX_TABLE; lntCrashedTable: img := ICONINDEX_CRASHED_TABLE; lntView: img := ICONINDEX_VIEW; @@ -4110,9 +3791,10 @@ begin lntFunction: img := ICONINDEX_STOREDFUNCTION; else img := -1; end; - VTRowDataListTables[i-1].ImageIndex := img; + VTRowDataListTables[i].ImageIndex := img; - ds.Next; + Results.Next; + Inc(i); end; ListTables.RootNodeCount := Length(VTRowDataListTables); ListTables.EndUpdate; @@ -4137,30 +3819,6 @@ begin end; -{*** - Execute a query and return a resultset - The currently active connection is used - - @param String The single SQL-query to be executed on the server -} -function TMainForm.ExecuteQuery(query: String): TDataSet; -begin - result := GetResults(query, false, false); -end; - - -{*** - Execute a query without returning a resultset - The currently active connection is used - - @param String The single SQL-query to be executed on the server -} -procedure TMainForm.ExecuteNonQuery(SQLQuery: String); -begin - ExecUpdateQuery(SQLQuery); -end; - - {*** Selection in ListTables is changing @@ -4191,8 +3849,8 @@ begin SelectedNodes := ListTables.GetSortedSelection(False); - actSQLhelp.Enabled := mysql_version >= 40100; - actImportCSV.Enabled := mysql_version >= 32206; + actSQLhelp.Enabled := Connection.ServerVersionInt >= 40100; + actImportCSV.Enabled := Connection.ServerVersionInt >= 32206; // Data tab - if query results are made editable, these will need // to be changed to look at which tab is focused. @@ -4260,7 +3918,7 @@ end; procedure TMainForm.CheckUptime; begin - ServerUptime := MakeInt(GetVar('SHOW STATUS LIKE ''Uptime''', 1)); + ServerUptime := MakeInt(Connection.GetVar('SHOW STATUS LIKE ''Uptime''', 1)); // Avoid division by zero ServerUptime := Max(ServerUptime, 1); TimerHostUptime.Enabled := true; @@ -4280,10 +3938,10 @@ begin for i := 0 to ProcessIDs.Count - 1 do begin // Don't kill own process - if ProcessIDs[i] = IntToStr( MySQLConn.Connection.GetThreadId ) then + if ProcessIDs[i] = IntToStr(Connection.ThreadId) then LogSQL('Ignoring own process id '+ProcessIDs[i]+' when trying to kill it.') else - ExecUpdateQuery( 'KILL '+ProcessIDs[i] ); + Connection.Query('KILL '+ProcessIDs[i]); end; ListProcesses.Tag := VTREE_NOTLOADED; ListProcesses.Repaint; @@ -4302,11 +3960,9 @@ var SQLscriptstart : Integer; SQLscriptend : Integer; SQLTime : Double; - LastVistaCheck : Cardinal; - VistaCheck : Boolean; fieldcount : Integer; recordcount : Integer; - ds : TDataSet; + Results : TMySQLQuery; ColName, Text, LB : WideString; col : TVirtualTreeColumn; @@ -4326,66 +3982,45 @@ begin if LB <> '' then Text := WideStringReplace(Text, CRLF, LB, [rfReplaceAll]); SQL := parseSQL(Text); - - if ( SQL.Count = 0 ) then - begin + if SQL.Count = 0 then begin ResultLabel.Caption := '(nothing to do)'; Exit; end; - SQLscriptstart := GetTickCount(); - LastVistaCheck := GetTickCount(); + SQLscriptstart := GetTickCount; ResultLabel.Caption := ''; - ds := nil; + Results := nil; try showstatus( 'Initializing SQL...' ); actExecuteQuery.Enabled := false; actExecuteSelection.Enabled := false; - // Let EnsureActiveDatabase know that we've fired user queries. - UserQueryFiring := true; - rowsaffected := 0; fieldcount := 0; recordcount := 0; EnableProgressBar(SQL.Count); - showstatus( 'Executing SQL...' ); - for i := 0 to (SQL.Count - 1) do - begin + showstatus('Executing SQL...'); + for i:=0 to SQL.Count-1 do begin ProgressBarStatus.StepIt; ProgressBarStatus.Repaint; - if ( sql[i] = '' ) then - begin + if SQL[i] = '' then continue; - end; - // open last query with data-aware: ResultLabel.Caption := ''; - // ok, let's rock - SQLstart := GetTickCount(); + SQLstart := GetTickCount; try - VistaCheck := false; - if GetTickCount() - LastVistaCheck > 2500 then begin - VistaCheck := true; - LastVistaCheck := GetTickCount(); - end; - ds := GetResults( SQL[i], false, false, VistaCheck ); - if ( ds <> nil ) then - begin - fieldcount := ds.Fieldcount; - recordcount := ds.Recordcount; - rowsaffected := rowsaffected + TZQuery(ds).RowsAffected; - end - else - begin + Results := Connection.GetResults(SQL[i]); + if Assigned(Results) then begin + fieldcount := Results.ColumnCount; + recordcount := Results.Recordcount; + end else begin fieldcount := 0; recordcount := 0; - rowsaffected := FMysqlConn.Connection.GetAffectedRowsFromLastPost; + rowsaffected := Connection.RowsAffected; end; except - on E:Exception do - begin + on E:Exception do begin if actQueryStopOnErrors.Checked or (i = SQL.Count - 1) then begin Screen.Cursor := crDefault; MessageDlg( E.Message, mtError, [mbOK], 0 ); @@ -4397,40 +4032,35 @@ begin end; end; - SQLend := GetTickCount(); + SQLend := GetTickCount; SQLTime := (SQLend - SQLstart) / 1000; ResultLabel.Caption := FormatNumber( rowsaffected ) +' row(s) affected, '+ FormatNumber( fieldcount ) +' column(s) x '+ FormatNumber( recordcount ) +' row(s) in last result set.'; - if ( SQL.Count = 1 ) then - begin - ResultLabel.Caption := ResultLabel.Caption + - ' Query time: '+ FormatNumber( SQLTime, 3) +' sec.'; - end; + if i < SQL.Count-1 then + FreeAndNil(Results); + if SQL.Count = 1 then + ResultLabel.Caption := ResultLabel.Caption + ' Query time: '+ FormatNumber( SQLTime, 3) +' sec.'; end; ProgressBarStatus.Hide; ValidateQueryControls(Sender); - if ( SQL.Count > 1 ) then + if SQL.Count > 1 then begin - SQLscriptend := GetTickCount(); + SQLscriptend := GetTickCount; SQLTime := (SQLscriptend - SQLscriptstart) / 1000; ResultLabel.Caption := ResultLabel.Caption +' Batch time: '+ - FormatNumber( SQLTime, 3 ) +' sec.'; + FormatNumber(SQLTime, 3) +' sec.'; end; finally - // Let EnsureActiveDatabase know that we've fired user queries. - UserQueryFired := true; - UserQueryFiring := false; - // Avoid excessive GridHighlightChanged() when flicking controls. viewingdata := true; - if ds <> nil then begin + if Assigned(Results) and Results.HasResult then begin ActiveGrid.BeginUpdate; ActiveGrid.Header.Options := ActiveGrid.Header.Options + [hoVisible]; ActiveGrid.Header.Columns.BeginUpdate; @@ -4438,44 +4068,35 @@ begin debug('mem: clearing and initializing query columns.'); ActiveGridResult := GridResult(ActiveGrid); SetLength(ActiveGridResult.Columns, 0); - SetLength(ActiveGridResult.Columns, ds.FieldCount); - for i:=0 to ds.FieldCount-1 do begin - ColName := ds.Fields[i].FieldName; + SetLength(ActiveGridResult.Columns, Results.ColumnCount); + for i:=0 to Results.ColumnCount-1 do begin + ColName := Results.ColumnNames[i]; col := ActiveGrid.Header.Columns.Add; col.Text := ColName; col.Options := col.Options - [coAllowClick]; ActiveGridResult.Columns[i].Name := ColName; - if ds.Fields[i].DataType in [ftSmallint, ftInteger, ftWord, ftLargeint] then begin - ActiveGridResult.Columns[i].DatatypeCat := dtcInteger; + ActiveGridResult.Columns[i].DatatypeCat := Results.DataType(i).Category; + if ActiveGridResult.Columns[i].DatatypeCat in [dtcInteger, dtcReal] then col.Alignment := taRightJustify; - end else if ds.Fields[i].DataType in [ftFloat] then begin - ActiveGridResult.Columns[i].DatatypeCat := dtcReal; - col.Alignment := taRightJustify; - end else if ds.Fields[i].DataType in [ftDate, ftTime, ftDateTime, ftTimeStamp] then - ActiveGridResult.Columns[i].DatatypeCat := dtcTemporal - else if ds.Fields[i].DataType in [ftWideString, ftMemo, ftWideMemo] then - ActiveGridResult.Columns[i].DatatypeCat := dtcText - else if ds.Fields[i].DataType in [ftBlob] then - ActiveGridResult.Columns[i].DatatypeCat := dtcBinary; end; debug('mem: query column initialization complete.'); debug('mem: clearing and initializing query rows (internal data).'); SetLength(ActiveGridResult.Rows, 0); - SetLength(ActiveGridResult.Rows, ds.RecordCount); - ds.First; - for i:=0 to ds.RecordCount-1 do begin + SetLength(ActiveGridResult.Rows, Results.RecordCount); + Results.First; + for i:=0 to Results.RecordCount-1 do begin ActiveGridResult.Rows[i].Loaded := True; - SetLength(ActiveGridResult.Rows[i].Cells, ds.FieldCount); - for j:=0 to ds.FieldCount-1 do begin + SetLength(ActiveGridResult.Rows[i].Cells, Results.ColumnCount); + for j:=0 to Results.ColumnCount-1 do begin if ActiveGridResult.Columns[j].DatatypeCat = dtcBinary then - ActiveGridResult.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString) + ActiveGridResult.Rows[i].Cells[j].Text := '0x' + BinToWideHex(Results.Col(j)) else - ActiveGridResult.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString; - ActiveGridResult.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull; + ActiveGridResult.Rows[i].Cells[j].Text := Results.Col(j); + ActiveGridResult.Rows[i].Cells[j].IsNull := Results.IsNull(j); end; - ds.Next; + Results.Next; end; - ds.Free; + Results.Free; debug('mem: initializing query rows (grid).'); ActiveGrid.RootNodeCount := Length(ActiveGridResult.Rows); debug('mem: query row initialization complete.'); @@ -4515,7 +4136,7 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; var CanExecute: Boolean); var i,j : Integer; - ds : TDataset; + Results : TMySQLQuery; sql, TableClauses: WideString; Tables : TStringList; tablename : WideString; @@ -4532,14 +4153,14 @@ var const ItemPattern: WideString = '\image{%d}\hspace{5}\color{clSilver}%s\column{}\color{clWindowText}%s'; - procedure addTable( Fields: TFields ); + procedure addTable(Results: TMySQLQuery); var ObjName, ObjType: WideString; Icon: Integer; begin - ObjName := Fields[0].AsWideString; + ObjName := Results.Col(0); ObjType := ''; - if Fields.FindField(DBO_TYPE) <> nil then - ObjType := LowerCase(Fields.FieldByName(DBO_TYPE).AsString); - case GetDBObjectType(Fields) of + if Results.ColExists(DBO_TYPE) then + ObjType := LowerCase(Results.Col(DBO_TYPE)); + case GetDBObjectType(Results) of lntTable: Icon := ICONINDEX_TABLE; lntCrashedTable: Icon := ICONINDEX_CRASHED_TABLE; lntFunction: Icon := ICONINDEX_STOREDFUNCTION; @@ -4554,8 +4175,7 @@ const procedure addColumns( tablename: WideString ); var dbname : WideString; - i : Integer; - ds : TDataSet; + Columns: TMySQLQuery; begin dbname := ActiveDatabase; if Pos( '.', tablename ) > -1 then @@ -4567,16 +4187,17 @@ const // Rely on what the user typed is already a valid masked/quoted identifier. if dbname <> '' then tablename := dbname + '.' + tablename; - ds := getResults( 'SHOW COLUMNS FROM '+tablename, true, false ); - if ds = nil then exit; - for i:=0 to ds.RecordCount-1 do - begin - Proposal.InsertList.Add( ds.FieldByName( 'Field' ).AsWideString ); - Proposal.ItemList.Add( WideFormat(ItemPattern, [ICONINDEX_FIELD, GetFirstWord(ds.FieldByName('Type').AsString), ds.FieldByName('Field').AsWideString]) ); - ds.Next; + try + Columns := Connection.GetResults('SHOW COLUMNS FROM '+tablename); + except + Exit; end; - ds.Close; - FreeAndNil(ds); + while not Columns.Eof do begin + Proposal.InsertList.Add(Columns.Col('Field')); + Proposal.ItemList.Add(WideFormat(ItemPattern, [ICONINDEX_FIELD, GetFirstWord(Columns.Col('Type')), Columns.Col('Field')]) ); + Columns.Next; + end; + FreeAndNil(Columns); end; begin @@ -4669,10 +4290,10 @@ begin if i > -1 then begin // Only display tables from specified db Screen.Cursor := crHourGlass; - ds := FetchDbTableList(Databases[i]); - while not ds.Eof do begin - addTable(ds.Fields); - ds.Next; + Results := FetchDbTableList(Databases[i]); + while not Results.Eof do begin + addTable(Results); + Results.Next; end; Screen.Cursor := crDefault; end; @@ -4687,10 +4308,10 @@ begin if ActiveDatabase <> '' then begin // Display tables from current db - ds := FetchActiveDbTableList; - while not ds.Eof do begin - addTable(ds.Fields); - ds.Next; + Results := FetchActiveDbTableList; + while not Results.Eof do begin + addTable(Results); + Results.Next; end; if Length(CurrentInput) = 0 then // assume that we have already a dbname in memo Proposal.Position := Databases.Count; @@ -4699,7 +4320,7 @@ begin // Add functions for i := 0 to Length(MySQLFunctions) - 1 do begin // Don't display unsupported functions here - if MySqlFunctions[i].Version > mysql_version then + if MySqlFunctions[i].Version > Connection.ServerVersionInt then continue; Proposal.InsertList.Add( MySQLFunctions[i].Name + MySQLFunctions[i].Declaration ); Proposal.ItemList.Add( WideFormat(ItemPattern, [ICONINDEX_FUNCTION, 'function', MySQLFunctions[i].Name + '\color{clSilver}' + MySQLFunctions[i].Declaration] ) ); @@ -4781,7 +4402,7 @@ begin try ensureValidIdentifier( NewText ); // rename table - ExecUpdateQuery( 'RENAME TABLE ' + mask(NodeData.Captions[0]) + ' TO ' + mask(NewText), False, False ); + Connection.Query('RENAME TABLE ' + mask(NodeData.Captions[0]) + ' TO ' + mask(NewText)); if SynSQLSyn1.TableNames.IndexOf( NewText ) = -1 then begin SynSQLSyn1.TableNames.Add(NewText); @@ -4803,15 +4424,12 @@ end; procedure TMainForm.TimerConnectedTimer(Sender: TObject); begin - if not TimerConnected.Enabled then begin + if Assigned(Connection) and Connection.Active then begin + // calculate and display connection-time + showstatus('Connected: ' + FormatTimeNumber((GetTickCount-Connection.ConnectionStarted) Div 1000), 2 ); + end else begin showstatus('Disconnected.', 2); - exit; end; - - inc(time_connected); - - // calculate and display connection-time - showstatus( 'Connected: ' + FormatTimeNumber(time_connected), 2 ); end; @@ -5036,7 +4654,7 @@ procedure TMainForm.popupHostPopup(Sender: TObject); begin Kill1.Enabled := (PageControlHost.ActivePage = tabProcessList) and Assigned(ListProcesses.FocusedNode); menuEditVariable.Enabled := False; - if mysql_version >= 40003 then + if Connection.ServerVersionInt >= 40003 then menuEditVariable.Enabled := (PageControlHost.ActivePage = tabVariables) and Assigned(ListVariables.FocusedNode) else menuEditVariable.Hint := STR_NOTSUPPORTED; @@ -5110,8 +4728,8 @@ begin menuShowSizeColumn.Visible := False; actSelectTreeBackground.Visible := False; end; - actCreateView.Enabled := actCreateView.Enabled and (mysql_version >= 50001); - actCreateRoutine.Enabled := actCreateRoutine.Enabled and (mysql_version >= 50003); + actCreateView.Enabled := actCreateView.Enabled and (Connection.ServerVersionInt >= 50001); + actCreateRoutine.Enabled := actCreateRoutine.Enabled and (Connection.ServerVersionInt >= 50003); end; @@ -5301,315 +4919,6 @@ begin end; -procedure TMainForm.ExecUseQuery(db: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false); -begin - ExecUpdateQuery('USE ' + mask(db), HandleErrors, DisplayErrors); - FConn.MysqlParams.Database := db; -end; - - -{*** - Execute a query without returning a resultset - The currently active connection is used - - @param String The single SQL-query to be executed on the server -} -function TMainForm.ExecUpdateQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false): Int64; -var - MysqlQuery : TMysqlQuery; - ds: TDataSet; -begin - Result := -1; // Silence compiler warning. - MysqlQuery := nil; - try - try - // Start query execution - MysqlQuery := RunThreadedQuery(sql, false); - Result := FMysqlConn.Connection.GetAffectedRowsFromLastPost; - // Inspect query result code and log / notify user on failure - if MysqlQuery.Result in [MQR_CONNECT_FAIL,MQR_QUERY_FAIL] then - begin - raise Exception.Create(MysqlQuery.Comment); - end; - except - on E: Exception do begin - LogSQL( E.Message, True ); - if DisplayErrors then MessageDlg( E.Message, mtError, [mbOK], 0 ); - // Recreate exception, since we free it below the caller - // won't know what happened otherwise. - if not HandleErrors then raise THandledSQLError.Create(MysqlQuery.Comment); - Result := -1; - end; - end; - finally - // Cleanup the MysqlQuery object, we won't need it anymore - if MysqlQuery <> nil then begin - if MysqlQuery.MysqlDataset <> nil then - MysqlQuery.MysqlDataset.Close; - ds := MysqlQuery.MysqlDataset; - FreeAndNil(ds); - end; - FreeAndNil (MysqlQuery); - end; -end; - - -{*** - Execute a query which may return a resultset. The caller is responsible for - freeing the MysqlQuery object and its Dataset member, only on returnvalue True. - The currently active connection is used - - @param String The single SQL-query to be executed on the server - @return TMysqlQuery Containing the dataset and info data availability -} -function TMainForm.ExecSelectQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet; -var - res: TMysqlQuery; -begin - res := nil; - result := nil; - try - try - // Start query execution - res := RunThreadedQuery(sql, ForceDialog); - result := res.MysqlDataset; - // Inspect query result code and log / notify user on failure - if res.Result in [MQR_CONNECT_FAIL,MQR_QUERY_FAIL] then - begin - raise Exception.Create(res.Comment); - end; - except - on E: Exception do begin - LogSQL( E.Message, True ); - if DisplayErrors then MessageDlg( E.Message, mtError, [mbOK], 0 ); - if not HandleErrors then raise THandledSQLError.Create(E.Message); - Result := nil; - end; - end; - finally - FreeAndNil(res); - end; -end; - - -{*** - Executes a query. -} -function TMainForm.GetResults( SQLQuery: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet; -begin - result := ExecSelectQuery(SQLQuery, HandleErrors, DisplayErrors, ForceDialog); -end; - - -{*** - Execute a query and return String from column x -} -function TMainForm.GetVar( SQLQuery: WideString; x: Integer = 0; HandleErrors: Boolean = false; DisplayErrors: Boolean = false) : WideString; -var - ds: TDataSet; -begin - ds := GetResults( SQLQuery, HandleErrors, DisplayErrors ); - if ds = nil then exit; - Result := ds.Fields[x].AsWideString; - ds.Close; - FreeAndNil(ds); -end; - - -function TMainForm.GetNamedVar( SQLQuery: WideString; x: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false) : WideString; -var - ds: TDataSet; -begin - ds := GetResults( SQLQuery, HandleErrors, DisplayErrors ); - if ds = nil then exit; - Result := ds.Fields.FieldByName(x).AsWideString; - ds.Close; - FreeAndNil(ds); -end; - - -{*** - Execute a query and return column x as Stringlist - - @param String SQL query String - @param Integer 0-based column index in the resultset to return - @return TStringList -} -function TMainForm.GetCol( SQLQuery: WideString; x: Integer = 0; HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideStrings.TWideStringList; -var - i: Integer; - ds: TDataSet; -begin - ds := GetResults( SQLQuery, HandleErrors, DisplayErrors); - Result := WideStrings.TWideStringList.Create; - if ds = nil then exit; - for i := 0 to ds.RecordCount - 1 do - begin - Result.Add( ds.Fields[x].AsWideString ); - ds.Next; - end; - ds.Close; - FreeAndNil(ds); -end; - - -{*** - Event procedure handler for the ZSQLMonitor1 object -} -procedure TMainForm.ZSQLMonitor1LogTrace(Sender: TObject; - Event: TZLoggingEvent); -begin - LogSQL( Event.Message, (Event.Category <> lcExecute) ); -end; - - -procedure TMainForm.RunAsyncPost(ds: TDeferDataSet); -var - res: TMysqlQuery; -begin - FQueryRunning := true; - try - try - CheckConnection; - except - on E: Exception do begin - raise Exception.Create('Failed to reconnect, giving up. (' + E.Message + ')'); - end; - end; - FProgressForm := TFrmQueryProgress.Create(Self); - debug('RunThreadedQuery(): Launching asynchronous query.'); - res := ExecPostAsync(FConn,FProgressForm.Handle,ds); - WaitForQueryCompletion(FProgressForm, res, false); - if res.Result in [MQR_CONNECT_FAIL,MQR_QUERY_FAIL] then - begin - raise Exception.Create(res.Comment); - end; - finally - FQueryRunning := false; - end; -end; - -{*** - Run a query in a separate thread of execution on the current connection. -} -function TMainForm.RunThreadedQuery(AQuery: WideString; ForceDialog: Boolean): TMysqlQuery; -begin - Result := nil; - if (Copy(AQuery, 1, 3) <> 'USE') then EnsureDatabase; - // Indicate a querythread is active (only one thread allow at this moment) - FQueryRunning := true; - try - // Check if the connection of the current window is still alive - // Otherwise reconnect - try - CheckConnection; - except - on E: Exception do begin - // Ensure auto-updating processlist is disabled, see bug #1865305 - if TimerRefresh.Enabled then - AutoRefreshToggle(nil); - Screen.Cursor := crDefault; - raise Exception.Create('Failed to reconnect, giving up. (' + E.Message + ')'); - end; - end; - - // Create instance of the progress form (but don't show it yet) - FProgressForm := TFrmQueryProgress.Create(Self); - - { Launch a thread of execution that passes the query to the server - - The progressform serves as receiver of the status - messages (WM_MYSQL_THREAD_NOTIFY) of the thread: - - * After the thread starts it notifies the progressform (MQE_INITED) - (which calls ShowModal on itself) - * Waits for a completion message from the thread (MQE_FINISHED) to remove itself - * Set FQueryRunning to false - } - debug('RunThreadedQuery(): Launching asynchronous query.'); - Result := ExecMysqlStatementAsync (AQuery,FConn,FProgressForm.Handle,RunAsyncPost); - - { Repeatedly check if the query has finished by inspecting FQueryRunning - Allow repainting of user interface - } - WaitForQueryCompletion(FProgressForm, Result, ForceDialog); - finally - FQueryRunning := false; - end; - // Hack: Un-prevent dynamic loading of records in the context of the wait form's message loop. - if not DataGrid.Visible then DataGrid.Visible := True; -end; - - -procedure TMainForm.CancelQuery; -begin - cancelling := true; - MysqlConn.Connection.CancelQuery; -end; - - -// Searchbox unfocused -procedure TMainForm.CheckConnection; -var - connected: Boolean; - choice: Integer; -begin - if not FMysqlConn.IsAlive then begin - LogSQL('Connection failure detected. Trying to reconnect.', true); - TimerConnected.Enabled := false; - TimerConnectedTimer(self); - TimerHostUptime.Enabled := false; - TimerHostUptimeTimer(self); - FQueryRunning := false; - try - FMysqlConn.Connection.Disconnect; - connected := True; - try - // CheckConnected() doesn't really check anything, it - // just sees if the driver has disposed of it's connection - // by means of a Disconnect() or not. In which case there - // is no point in doing a Reconnect(), it will NOP. - FMysqlConn.Connection.CheckConnected; - except - connected := False; - end; - while not FMysqlConn.IsAlive do begin - try - if connected then FMysqlConn.Connection.Reconnect - else FMysqlConn.Connection.Connect; - except - on E: Exception do begin - MainForm.Visible := False; - choice := MessageDlg( - 'Connection to the server has been lost.'#10#10 + - E.Message + #10#10 + - 'Click Abort to exit this session.', - mtError, - [mbRetry, mbAbort], 0 - ); - if choice = mrAbort then begin - Close; - Halt(1); - end; - end; - end; - if FMysqlConn.IsAlive then MainForm.Visible := True; - end; - - time_connected := 0; - TimerConnected.Enabled := true; - LogSQL('Connected. Thread-ID: ' + IntToStr( MySQLConn.Connection.GetThreadId )); - CheckUptime; - // Try to restore active database - if ActiveDatabase <> '' then - ExecUseQuery(ActiveDatabase) - finally - FQueryRunning := true; - end; - end; -end; - - function TMainForm.GetActiveDatabase: WideString; var s: PVirtualNode; @@ -5640,15 +4949,15 @@ end; function TMainForm.GetTreeNodeType(Node: PVirtualNode): TListNodeType; var - ds: TDataset; + Results: TMySQLQuery; begin Result := lntNone; if Assigned(Node) then case DBtree.GetNodeLevel(Node) of 1: Result := lntDb; 2: begin - ds := FetchDbTableList(DBTree.Text[Node.Parent, 0]); - ds.RecNo := Node.Index+1; - Result := GetDBObjectType(ds.Fields); + Results := FetchDbTableList(DBTree.Text[Node.Parent, 0]); + Results.RecNo := Node.Index; + Result := GetDBObjectType(Results); end; end; end; @@ -5786,7 +5095,7 @@ begin if (SelectedTable.Text <> '') and Assigned(SelectedTableColumns) then begin SelectedTableColumns.First; while not SelectedTableColumns.Eof do begin - ActiveQueryHelpers.Items.Add(SelectedTableColumns.Fields[0].AsWideString); + ActiveQueryHelpers.Items.Add(SelectedTableColumns.Col(0)); SelectedTableColumns.Next; end; end; @@ -5799,7 +5108,7 @@ begin for i := 0 to Length(MySQLFunctions) - 1 do begin // Don't display unsupported functions here - if MySqlFunctions[i].Version > mysql_version then + if MySqlFunctions[i].Version > Connection.ServerVersionInt then continue; ActiveQueryHelpers.Items.Add( MySQLFunctions[i].Name + MySQLFunctions[i].Declaration ); end; @@ -6529,21 +5838,20 @@ begin Combobox.Items.Clear; // Cache datasets - if ((dsShowEngines = nil) or (dsShowEngines.State = dsInactive)) and - ((dsHaveEngines = nil) or (dsHaveEngines.State = dsInactive)) then - begin + if dsShowEngines = nil then begin FreeAndNil(dsShowEngines); + dsShowEngines := Connection.GetResults('SHOW ENGINES'); + end; + if dsHaveEngines = nil then begin FreeAndNil(dsHaveEngines); - dsShowEngines := GetResults('SHOW ENGINES', True); - if dsShowEngines = nil then - dsHaveEngines := GetResults('SHOW VARIABLES LIKE ''have%'''); + dsHaveEngines := Connection.GetResults('SHOW VARIABLES LIKE ''have%'''); end; - if dsShowEngines <> nil then begin + if Assigned(dsShowEngines) then begin dsShowEngines.First; while not dsShowEngines.Eof do begin - engineName := dsShowEngines.FieldByName('Engine').AsString; - engineSupport := LowerCase(dsShowEngines.FieldByName('Support').AsString); + engineName := dsShowEngines.Col('Engine'); + engineSupport := LowerCase(dsShowEngines.Col('Support')); // Add to dropdown if supported if engineSupport <> 'no' then Combobox.Items.Add(engineName); @@ -6566,14 +5874,14 @@ begin HaveEngineList.CommaText := 'ARCHIVE,BDB,BLACKHOLE,CSV,EXAMPLE,FEDERATED,INNODB,ISAM'; dsHaveEngines.First; while not dsHaveEngines.Eof do begin - engineName := copy(dsHaveEngines.Fields[0].AsString, 6, Length(dsHaveEngines.Fields[0].AsString) ); + engineName := copy(dsHaveEngines.Col(0), 6, Length(dsHaveEngines.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 dropdown if it's a) in HaveEngineList and b) activated if (HaveEngineList.IndexOf(engineName) > -1) - and (LowerCase(dsHaveEngines.Fields[1].AsString) = 'yes') then + and (LowerCase(dsHaveEngines.Col(1)) = 'yes') then Combobox.Items.Add(engineName); dsHaveEngines.Next; end; @@ -6711,7 +6019,7 @@ begin EditVariableForm.VarValue := NodeData.Captions[1]; // Refresh relevant list node if EditVariableForm.ShowModal = mrOK then - NodeData.Captions[1] := GetVar('SHOW VARIABLES LIKE '+esc(NodeData.Captions[0]), 1); + NodeData.Captions[1] := Connection.GetVar('SHOW VARIABLES LIKE '+esc(NodeData.Captions[0]), 1); end; @@ -6732,7 +6040,7 @@ procedure TMainForm.DBtreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString); var - ds: TDataset; + Results: TMySQLQuery; db, eng: WideString; i: Integer; Bytes: Int64; @@ -6740,12 +6048,12 @@ var begin case Column of 0: case Sender.GetNodeLevel(Node) of - 0: CellText := FConn.MysqlParams.User + '@' + FConn.MysqlParams.Host; + 0: CellText := Connection.Username + '@' + Connection.Hostname; 1: CellText := Databases[Node.Index]; 2: begin - ds := FetchDbTableList(Databases[Node.Parent.Index]); - ds.RecNo := Node.Index+1; - CellText := ds.FieldByName(DBO_NAME).AsWideString; + Results := FetchDbTableList(Databases[Node.Parent.Index]); + Results.RecNo := Node.Index; + CellText := Results.Col(DBO_NAME); end; end; 1: case GetTreeNodeType(Node) of @@ -6763,10 +6071,10 @@ begin if AllListsCached then begin Bytes := 0; for i := 0 to Databases.Count - 1 do begin - ds := FetchDbTableList(Databases[i]); - while not ds.Eof do begin - Bytes := Bytes + GetTableSize(ds); - ds.Next; + Results := FetchDbTableList(Databases[i]); + while not Results.Eof do begin + Bytes := Bytes + GetTableSize(Results); + Results.Next; end; end; end; @@ -6780,13 +6088,13 @@ begin CellText := '' else begin Bytes := 0; - ds := FetchDbTableList(db); - while not ds.Eof do begin - if ds.FindField('Type') <> nil then eng := FieldContent(ds, 'Type') - else eng := FieldContent(ds, 'Engine'); + Results := FetchDbTableList(db); + while not Results.Eof do begin + if Results.ColExists(DBO_TYPE) then eng := Results.Col(DBO_TYPE) + else eng := Results.Col('Engine'); if UpperCase(eng) <> 'MRG_MYISAM' then - Bytes := Bytes + GetTableSize(ds); - ds.Next; + Bytes := Bytes + GetTableSize(Results); + Results.Next; end; if Bytes >= 0 then CellText := FormatByteNumber(Bytes) else CellText := ''; @@ -6794,9 +6102,9 @@ begin end; lntTable: begin db := DBtree.Text[Node.Parent, 0]; - ds := FetchDbTableList(db); - ds.RecNo := Node.Index + 1; - Bytes := GetTableSize(ds); + Results := FetchDbTableList(db); + Results.RecNo := Node.Index + 1; + Bytes := GetTableSize(Results); CellText := FormatByteNumber(Bytes); end else CellText := ''; // Applies for views and crashed tables @@ -6812,7 +6120,7 @@ procedure TMainForm.DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); var - ds: TDataset; + Results: TMySQLQuery; begin if Column > 0 then Exit; @@ -6822,9 +6130,9 @@ begin ImageIndex := ICONINDEX_DB_HIGHLIGHT else ImageIndex := ICONINDEX_DB; 2: begin - ds := FetchDbTableList(Databases[Node.Parent.Index]); - ds.RecNo := Node.Index+1; - case GetDBObjectType(ds.Fields) of + Results := FetchDbTableList(Databases[Node.Parent.Index]); + Results.RecNo := Node.Index; + case GetDBObjectType(Results) of lntTable: if Kind = ikSelected then ImageIndex := ICONINDEX_TABLE_HIGHLIGHT @@ -6854,7 +6162,7 @@ procedure TMainForm.DBtreeInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal); var VT: TVirtualStringTree; - ds: TDataset; + Results: TMySQLQuery; i, j: Integer; DatabasesWanted: TWideStringList; rx: TRegExpr; @@ -6868,7 +6176,7 @@ begin try if not Assigned(AllDatabases) then begin Showstatus( 'Reading Databases...' ); - AllDatabases := GetCol('SHOW DATABASES'); + AllDatabases := Connection.GetCol('SHOW DATABASES'); end; if not Assigned(Databases) then Databases := TWideStringList.Create; @@ -6917,8 +6225,8 @@ begin Screen.Cursor := crHourglass; Showstatus( 'Reading Tables...' ); try - ds := FetchDbTableList(Databases[Node.Index]); - ChildCount := ds.RecordCount; + Results := FetchDbTableList(Databases[Node.Index]); + ChildCount := Results.RecordCount; finally ShowStatus( STATUS_MSG_READY ); Screen.Cursor := crDefault; @@ -6969,10 +6277,12 @@ begin 0: ShowHost; 1: begin newDb := Databases[Node.Index]; + Connection.Database := newDb; ShowDatabase( newDb ); end; 2: begin newDb := Databases[Node.Parent.Index]; + Connection.Database := newDb; newDbObject := SelectedTable.Text; tabEditor.TabVisible := True; tabData.TabVisible := SelectedTable.NodeType in [lntTable, lntCrashedTable, lntView]; @@ -7005,6 +6315,13 @@ begin end; +procedure TMainForm.DatabaseChanged(Database: WideString); +begin + if Database <> ActiveDatabase then + ActiveDatabase := Database; +end; + + procedure TMainForm.DBtreeDblClick(Sender: TObject); var Node: PVirtualNode; @@ -7177,21 +6494,8 @@ end; function TMainForm.DbTableListCachedAndValid(db: WideString): Boolean; -var - ds: TDataSet; begin Result := CachedTableLists.IndexOf(db) > -1; - if Result then begin - ds := TDataSet(CachedTableLists.Objects[CachedTableLists.IndexOf(db)]); - // Delphi's RTL (TDataSet in DB.pas) throws exceptions right and left - // if the database the dataset(-derivate, aka TZDataSet) came from is - // currently, or has been earlier been, disconnected. Therefore, nuke - // these datasets, they'll have to be reloaded. - if ds.State = dsInactive then begin - ClearDbTableList(db); - Result := False; - end; - end; end; procedure TMainForm.editFilterSearchChange(Sender: TObject); @@ -7208,7 +6512,7 @@ begin for i := 0 to SelectedTableColumns.RecordCount - 1 do begin if i > 0 then Add := Add + ' OR '; - Add := Add + mask(SelectedTableColumns.Fields[0].AsWideString) + ' LIKE ' + esc('%'+ed.Text+'%'); + Add := Add + mask(SelectedTableColumns.Col(0)) + ' LIKE ' + esc('%'+ed.Text+'%'); if Length(Add) > 45 then begin Clause := Clause + Add + CRLF; Add := ''; @@ -7261,7 +6565,7 @@ procedure TMainForm.EnsureNodeLoaded(Sender: TBaseVirtualTree; Node: PVirtualNod var res: TGridResult; query: WideString; - ds: TDataSet; + Results: TMySQLQuery; i, j: LongInt; begin res := GridResult(Sender); @@ -7276,9 +6580,9 @@ begin // start query ShowStatus('Retrieving data...'); - ds := GetResults(query); + Results := Connection.GetResults(query); // If new data does not match current filter, remove from tree. - if Cardinal(ds.RecordCount) < 1 then begin + if Results.RecordCount < 1 then begin // Remove entry from dynamic array. for i := Node.Index to Length(res.Rows) - 1 do begin if i < Length(res.Rows) - 1 then res.Rows[i] := res.Rows[i + 1]; @@ -7290,21 +6594,21 @@ begin // fill in data ShowStatus('Filling grid with record-data...'); - if Cardinal(ds.RecordCount) > 0 then begin - SetLength(res.Rows[Node.Index].Cells, ds.Fields.Count); + if Results.RecordCount > 0 then begin + SetLength(res.Rows[Node.Index].Cells, Results.ColumnCount); i := Node.Index; - for j := 0 to ds.Fields.Count - 1 do begin + for j := 0 to Results.ColumnCount - 1 do begin if res.Columns[j].DatatypeCat = dtcBinary then - res.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString) + res.Rows[i].Cells[j].Text := '0x' + BinToWideHex(Results.Col(j)) else - res.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString; - res.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull; + res.Rows[i].Cells[j].Text := Results.Col(j); + res.Rows[i].Cells[j].IsNull := Results.IsNull(j); end; res.Rows[Node.Index].Loaded := True; end; ShowStatus( STATUS_MSG_READY ); - FreeAndNil(ds); + FreeAndNil(Results); end; end; @@ -7313,7 +6617,7 @@ var res: TGridResult; start, limit: Cardinal; query: WideString; - ds: TDataSet; + Results: TMySQLQuery; i, j: LongInt; hi: LongInt; regCrashIndicName: String; @@ -7340,7 +6644,7 @@ begin ShowStatus('Retrieving data...'); debug(Format('mem: loading data chunk from row %d to %d', [start, limit])); try - ds := GetResults(query); + Results := Connection.GetResults(query); except // if something bad happened, nuke cache, reset cursor and display error. TVirtualStringTree(Sender).RootNodeCount := 0; @@ -7350,8 +6654,8 @@ begin Screen.Cursor := crDefault; raise; end; - if Cardinal(ds.RecordCount) < limit then begin - limit := ds.RecordCount; + if Cardinal(Results.RecordCount) < limit then begin + limit := Results.RecordCount; TVirtualStringTree(Sender).RootNodeCount := start + limit; SetLength(res.Rows, start + limit); ReachedEOT := true; @@ -7372,17 +6676,17 @@ begin // fill in data ShowStatus('Filling grid with record-data...'); - for i := start to start + limit - 1 do begin - SetLength(res.Rows[i].Cells, ds.Fields.Count); - for j := 0 to ds.Fields.Count - 1 do begin + for i:=start to start+limit-1 do begin + SetLength(res.Rows[i].Cells, Results.ColumnCount); + for j:=0 to Results.ColumnCount-1 do begin if res.Columns[j].DatatypeCat = dtcBinary then - res.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString) + res.Rows[i].Cells[j].Text := '0x' + BinToWideHex(Results.Col(j)) else - res.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString; - res.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull; + res.Rows[i].Cells[j].Text := Results.Col(j); + res.Rows[i].Cells[j].IsNull := Results.IsNull(j); end; res.Rows[i].Loaded := True; - ds.Next; + Results.Next; end; if res = DataGridResult then begin @@ -7391,7 +6695,7 @@ begin end; ShowStatus( STATUS_MSG_READY ); - FreeAndNil(ds); + FreeAndNil(Results); end; end; @@ -7676,7 +6980,8 @@ begin sql := sql + ' WHERE ' + GetWhereClause(Row, @DataGridResult.Columns); try // Send UPDATE query - if (ExecUpdateQuery(sql, False, True) = 0) then begin + Connection.Query(sql); + if Connection.RowsAffected = 0 then begin MessageDlg('Your change did not affect any row! This can have several causes:' + CRLF + CRLF + 'a) Your changes were silently converted by the server. For instance, if you tried to ' + 'update an unsigned TINYINT field from its maximum value 255 to a higher value.' + CRLF + CRLF + @@ -7776,8 +7081,8 @@ var Result.Clear; SelectedTableKeys.First; while not SelectedTableKeys.Eof do begin - if SelectedTableKeys.FieldByName('Key_name').AsWideString = KeyName then - Result.Add(SelectedTableKeys.FieldByName('Column_name').AsWideString); + if SelectedTableKeys.Col('Key_name') = KeyName then + Result.Add(SelectedTableKeys.Col('Column_name')); SelectedTableKeys.Next; end; end; @@ -7788,8 +7093,8 @@ begin SelectedTableKeys.First; // 1. round: find a primary key while not SelectedTableKeys.Eof do begin - if SelectedTableKeys.FieldByName('Key_name').AsWideString = 'PRIMARY' then begin - FindColumns(SelectedTableKeys.FieldByName('Key_name').AsWideString); + if SelectedTableKeys.Col('Key_name') = 'PRIMARY' then begin + FindColumns(SelectedTableKeys.Col('Key_name')); Exit; end; SelectedTableKeys.Next; @@ -7797,16 +7102,16 @@ begin // no primary key available -> 2. round: find a unique key SelectedTableKeys.First; while not SelectedTableKeys.Eof do begin - if SelectedTableKeys.FieldByName('Non_unique').AsInteger = 0 then begin + if MakeInt(SelectedTableKeys.Col('Non_unique')) = 0 then begin // We found a UNIQUE key - better than nothing. Check if one of the key // columns allows NULLs which makes it dangerous to use in UPDATES + DELETES. - FindColumns(SelectedTableKeys.FieldByName('Key_name').AsWideString); + FindColumns(SelectedTableKeys.Col('Key_name')); SelectedTableColumns.First; AllowsNull := False; - for i := 0 to Result.Count - 1 do begin + for i:=0 to Result.Count-1 do begin while (not SelectedTableColumns.Eof) and (not AllowsNull) do begin - if SelectedTableColumns.FieldByName('Field').AsWideString = Result[i] then - AllowsNull := UpperCase(SelectedTableColumns.FieldByName('Null').AsString) = 'YES'; + if SelectedTableColumns.Col('Field') = Result[i] then + AllowsNull := UpperCase(SelectedTableColumns.Col('Null')) = 'YES'; SelectedTableColumns.Next; end; if AllowsNull then break; @@ -7892,9 +7197,9 @@ begin Cols := Copy(Cols, 1, Length(Cols)-2); sql := 'INSERT INTO '+mask(DataGridDB)+'.'+mask(DataGridTable)+' ('+Cols+') VALUES ('+Vals+')'; // Send INSERT query - if (ExecUpdateQuery(sql) = 0) then begin + Connection.Query(sql); + if Connection.RowsAffected = 0 then MessageBox(Self.Handle, 'Server failed to insert row.', 'Error', 0); - end; Result := True; Row.Loaded := false; EnsureNodeLoaded(Sender, Node, GetWhereClause(Row, @DataGridResult.Columns)); @@ -7928,7 +7233,7 @@ begin try // Send DELETE query - ExecUpdateQuery(sql, False, True); + Connection.Query(sql); Result := True; except Result := False; @@ -7936,7 +7241,7 @@ begin if Result then begin // Remove deleted row nodes out of the grid - Affected := FMysqlConn.Connection.GetAffectedRowsFromLastPost; + Affected := Connection.RowsAffected; Selected := Sender.SelectedCount; if Affected = Selected then begin // Fine. Number of deleted rows equals the selected node count. @@ -8016,7 +7321,7 @@ var Col: PGridColumn; sql: WideString; len: Int64; - ds: TDataSet; + Results: TMySQLQuery; begin Result := True; @@ -8038,10 +7343,10 @@ begin ' FROM ' + mask(SelectedTable.Text) + ' WHERE ' + GetWhereClause(Row, @DataGridResult.Columns) ; - ds := GetResults(sql); - if Col.DatatypeCat = dtcBinary then Cell.Text := '0x' + BinToWideHex(ds.Fields[0].AsString) - else Cell.Text := ds.Fields[0].AsWideString; - Cell.IsNull := ds.Fields[0].IsNull; + Results := Connection.GetResults(sql); + if Col.DatatypeCat = dtcBinary then Cell.Text := '0x' + BinToWideHex(Results.Col(0)) + else Cell.Text := Results.Col(0); + Cell.IsNull := Results.IsNull(0); end else Result := False; end; @@ -8127,27 +7432,25 @@ begin end; -function TMainForm.GetSelectedTableColumns: TDataset; +function TMainForm.GetSelectedTableColumns: TMySQLQuery; begin - if (FSelectedTableColumns = nil) or (FSelectedTableColumns.State = dsInactive) then begin - FreeAndNil(FSelectedTableColumns); - // Avoid SQL error on routines + if not Assigned(FSelectedTableColumns) then begin + // Avoid SQL error on routines if GetFocusedTreeNodeType in [lntTable, lntView] then begin ShowStatus('Reading table columns ...'); - FSelectedTableColumns := GetResults( 'SHOW /*!32332 FULL */ COLUMNS FROM ' + mask(SelectedTable.Text), false ); + FSelectedTableColumns := Connection.GetResults('SHOW /*!32332 FULL */ COLUMNS FROM ' + mask(SelectedTable.Text)); end; end; Result := FSelectedTableColumns; end; -function TMainForm.GetSelectedTableKeys: TDataset; +function TMainForm.GetSelectedTableKeys: TMySQLQuery; begin - if (FSelectedTableKeys = nil) or (FSelectedTableKeys.State = dsInactive) then begin - FreeAndNil(FSelectedTableKeys); - // Avoid SQL error on routines + if not Assigned(FSelectedTableKeys) then begin + // Avoid SQL error on routines if GetFocusedTreeNodeType in [lntTable, lntView] then begin ShowStatus('Reading table keys ...'); - FSelectedTableKeys := GetResults( 'SHOW KEYS FROM ' + mask(SelectedTable.Text) ); + FSelectedTableKeys := Connection.GetResults('SHOW KEYS FROM ' + mask(SelectedTable.Text)); end; end; Result := FSelectedTableKeys; @@ -8370,7 +7673,7 @@ end; procedure TMainForm.LoadDataView(ViewName: String); var rx: TRegExpr; - idx, i: Integer; + idx: Integer; Col: WideString; HiddenCols: TWideStringList; begin @@ -8383,8 +7686,8 @@ begin HiddenCols.DelimitedText := Utf8Decode(MainReg.ReadString(REGNAME_HIDDENCOLUMNS)); SelectedTableColumns.First; FDataGridSelect.Clear; - for i := 0 to SelectedTableColumns.RecordCount - 1 do begin - Col := SelectedTableColumns.Fields[0].AsWideString; + while not SelectedTableColumns.Eof do begin + Col := SelectedTableColumns.Col(0); if HiddenCols.IndexOf(Col) = -1 then FDataGridSelect.Add(Col); SelectedTableColumns.Next; @@ -8462,7 +7765,7 @@ procedure TMainForm.ListVariablesBeforePaint(Sender: TBaseVirtualTree; TargetCan var i : Integer; vt: TVirtualStringTree; - ds: TDataSet; + Results: TMySQLQuery; Sel: TWideStringList; begin // Display server variables @@ -8473,17 +7776,16 @@ begin DeInitializeVTNodes(vt); Screen.Cursor := crHourglass; try - ds := GetResults('SHOW VARIABLES'); - SetLength(VTRowDataListVariables, ds.RecordCount); - for i:=1 to ds.RecordCount do begin - VTRowDataListVariables[i-1].ImageIndex := 25; - VTRowDataListVariables[i-1].Captions := WideStrings.TWideStringList.Create; - VTRowDataListVariables[i-1].Captions.Add( ds.Fields[0].AsWideString ); - VTRowDataListVariables[i-1].Captions.Add( ds.Fields[1].AsWideString ); - ds.Next; + Results := Connection.GetResults('SHOW VARIABLES'); + SetLength(VTRowDataListVariables, Results.RecordCount); + for i:=0 to Results.RecordCount-1 do begin + VTRowDataListVariables[i].ImageIndex := 25; + VTRowDataListVariables[i].Captions := TWideStringList.Create; + VTRowDataListVariables[i].Captions.Add(Results.Col(0)); + VTRowDataListVariables[i].Captions.Add(Results.Col(1)); + Results.Next; end; - ds.Close; - FreeAndNil(ds); + FreeAndNil(Results); vt.RootNodeCount := Length(VTRowDataListVariables); vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection); SetVTSelection(vt, Sel); @@ -8504,7 +7806,7 @@ var i: Integer; valcount: Int64; tmpval: Double; - ds: TDataSet; + Results: TMySQLQuery; val, avg_perhour, avg_persec: WideString; valIsBytes, valIsNumber: Boolean; vt: TVirtualStringTree; @@ -8518,19 +7820,19 @@ begin DeInitializeVTNodes(vt); Screen.Cursor := crHourglass; try - ds := GetResults( 'SHOW /*!50002 GLOBAL */ STATUS' ); - SetLength(VTRowDataListStatus, ds.RecordCount); - for i:=1 to ds.RecordCount do begin - VTRowDataListStatus[i-1].ImageIndex := 25; - VTRowDataListStatus[i-1].Captions := WideStrings.TWideStringList.Create; - VTRowDataListStatus[i-1].Captions.Add( ds.Fields[0].AsWideString ); - val := ds.Fields[1].AsWideString; + Results := Connection.GetResults('SHOW /*!50002 GLOBAL */ STATUS'); + SetLength(VTRowDataListStatus, Results.RecordCount); + for i:=0 to Results.RecordCount-1 do begin + VTRowDataListStatus[i].ImageIndex := 25; + VTRowDataListStatus[i].Captions := TWideStringList.Create; + VTRowDataListStatus[i].Captions.Add(Results.Col(0)); + val := Results.Col(1); avg_perhour := ''; avg_persec := ''; // Detect value type valIsNumber := IntToStr(MakeInt(val)) = val; - valIsBytes := valIsNumber and (Copy(ds.Fields[0].AsWideString, 1, 6) = 'Bytes_'); + valIsBytes := valIsNumber and (Copy(Results.Col(0), 1, 6) = 'Bytes_'); // Calculate average values ... if valIsNumber then begin @@ -8551,13 +7853,12 @@ begin else if valIsNumber then val := FormatNumber(val); - VTRowDataListStatus[i-1].Captions.Add( val ); - VTRowDataListStatus[i-1].Captions.Add(avg_perhour); - VTRowDataListStatus[i-1].Captions.Add(avg_persec); - ds.Next; + VTRowDataListStatus[i].Captions.Add( val ); + VTRowDataListStatus[i].Captions.Add(avg_perhour); + VTRowDataListStatus[i].Captions.Add(avg_persec); + Results.Next; end; - ds.Close; - FreeAndNil(ds); + FreeAndNil(Results); // Tell VirtualTree the number of nodes it will display vt.RootNodeCount := Length(VTRowDataListStatus); vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection); @@ -8576,7 +7877,7 @@ end; procedure TMainForm.ListProcessesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); var i, j: Integer; - ds: TDataSet; + Results: TMySQLQuery; vt: TVirtualStringTree; Sel: TWideStringList; begin @@ -8588,25 +7889,24 @@ begin DeInitializeVTNodes(vt); Screen.Cursor := crHourglass; try - ds := GetResults('SHOW FULL PROCESSLIST', false, false); - SetLength(VTRowDataListProcesses, ds.RecordCount); - for i:=1 to ds.RecordCount do begin - VTRowDataListProcesses[i-1].Captions := WideStrings.TWideStringList.Create; - VTRowDataListProcesses[i-1].Captions.Add( ds.Fields[0].AsWideString ); - if AnsiCompareText( ds.Fields[4].AsString, 'Killed') = 0 then - VTRowDataListProcesses[i-1].ImageIndex := 26 // killed + Results := Connection.GetResults('SHOW FULL PROCESSLIST'); + SetLength(VTRowDataListProcesses, Results.RecordCount); + for i:=0 to Results.RecordCount-1 do begin + VTRowDataListProcesses[i].Captions := TWideStringList.Create; + VTRowDataListProcesses[i].Captions.Add(Results.Col(0)); + if AnsiCompareText(Results.Col(4), 'Killed') = 0 then + VTRowDataListProcesses[i].ImageIndex := 26 // killed else begin - if ds.FindField('Info').AsString = '' then - VTRowDataListProcesses[i-1].ImageIndex := 55 // idle + if Results.Col('Info') = '' then + VTRowDataListProcesses[i].ImageIndex := 55 // idle else - VTRowDataListProcesses[i-1].ImageIndex := 57 // running query + VTRowDataListProcesses[i].ImageIndex := 57 // running query end; for j := 1 to 7 do - VTRowDataListProcesses[i-1].Captions.Add(ds.Fields[j].AsWideString); - ds.Next; + VTRowDataListProcesses[i].Captions.Add(Results.Col(j)); + Results.Next; end; - ds.Close; - FreeAndNil(ds); + FreeAndNil(Results); vt.RootNodeCount := Length(VTRowDataListProcesses); vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection); SetVTSelection(vt, Sel); @@ -8655,7 +7955,7 @@ procedure TMainForm.ListCommandStatsBeforePaint(Sender: TBaseVirtualTree; Target var i: Integer; questions: Int64; - ds: TDataSet; + Results: TMySQLQuery; vt: TVirtualStringTree; Sel: TWideStringList; begin @@ -8668,18 +7968,17 @@ begin DeInitializeVTNodes(vt); Screen.Cursor := crHourglass; try - ds := GetResults('SHOW /*!50002 GLOBAL */ STATUS LIKE ''Com\_%''' ); - questions := MakeInt(GetVar('SHOW /*!50002 GLOBAL */ STATUS LIKE ''Questions''', 1)); + Results := Connection.GetResults('SHOW /*!50002 GLOBAL */ STATUS LIKE ''Com\_%''' ); + questions := MakeInt(Connection.GetVar('SHOW /*!50002 GLOBAL */ STATUS LIKE ''Questions''', 1)); if questions = 0 then Raise Exception.Create('Could not detect value of "Questions" status. Command statistics are not available.'); - SetLength(VTRowDataListCommandStats, ds.RecordCount+1); + SetLength(VTRowDataListCommandStats, Results.RecordCount+1); addLVitem(0, ' All commands', questions, questions ); - for i:=1 to ds.RecordCount do begin - addLVitem(i, ds.Fields[0].AsWideString, MakeInt(ds.Fields[1].AsString), questions ); - ds.Next; + for i:=0 to Results.RecordCount-1 do begin + addLVitem(i, Results.Col(0), MakeInt(Results.Col(1)), questions ); + Results.Next; end; - ds.Close; - FreeAndNil(ds); + FreeAndNil(Results); // Tell VirtualTree the number of nodes it will display vt.RootNodeCount := Length(VTRowDataListCommandStats); vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection); @@ -8929,7 +8228,7 @@ begin query := 'SELECT COUNT(*)' + DataGridCurrentFrom; if DataGridCurrentFilter <> '' then query := query + ' WHERE ' + DataGridCurrentFilter; try - count := MakeInt(GetVar(query)); + count := MakeInt(Connection.GetVar(query)); // Work around a memory allocation bug in VirtualTree. if count > prefMaxTotalRows then count := prefMaxTotalRows; except @@ -8970,18 +8269,16 @@ begin end; -function TMainform.GetCollations(Items: TWideStrings = nil): TDataset; +function TMainform.GetCollations(Items: TWideStrings = nil): TMySQLQuery; begin // Return cached collation list, used in several places, e.g. table editor - if (dsCollations = nil) or (dsCollations.State = dsInactive) then begin - FreeAndNil(dsCollations); - dsCollations := GetResults('SHOW COLLATION', True); - end; + if dsCollations = nil then + dsCollations := Connection.GetResults('SHOW COLLATION'); if Assigned(dsCollations) then begin dsCollations.First; if Assigned(Items) then begin while not dsCollations.Eof do begin - Items.Add(dsCollations.FieldByName('Collation').AsWideString); + Items.Add(dsCollations.Col('Collation')); dsCollations.Next; end; dsCollations.First; @@ -9640,7 +8937,6 @@ begin MakeVisible := Sender <> btnCloseFilterPanel; pnlFilterVT.Visible := MakeVisible; pnlFilterVT.Tag := Integer(MakeVisible); - ValidateControls(Sender); // On startup, we cannot SetFocus, throws exceptons. Call with nil in that special case - see FormCreate if Assigned(Sender) and MakeVisible then editFilterVT.SetFocus; diff --git a/extra/mysql_dataset/mysql_api.pas b/source/mysql_api.pas similarity index 97% rename from extra/mysql_dataset/mysql_api.pas rename to source/mysql_api.pas index 186ef7e8..1b716d8a 100644 --- a/extra/mysql_dataset/mysql_api.pas +++ b/source/mysql_api.pas @@ -195,7 +195,10 @@ type MYSQL_OPT_USE_EMBEDDED_CONNECTION, MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, - MYSQL_SECURE_AUTH + MYSQL_SECURE_AUTH, + MYSQL_REPORT_DATA_TRUNCATION, + MYSQL_OPT_RECONNECT, + MYSQL_OPT_SSL_VERIFY_SERVER_CERT ); TMySQLRplType = ( diff --git a/source/mysql_connection.pas b/source/mysql_connection.pas new file mode 100644 index 00000000..e07b2e24 --- /dev/null +++ b/source/mysql_connection.pas @@ -0,0 +1,728 @@ +unit mysql_connection; + +{$M+} // Needed to add published properties + +interface + +uses + Classes, SysUtils, windows, mysql_api, mysql_structures, WideStrings, WideStrUtils; + +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]; + +type + TMySQLQuery = class; + TMySQLConnection = class(TComponent) + private + FHandle: PMYSQL; + FActive: Boolean; + FConnectionStarted: Cardinal; + FHostname: String; + FSocketname: String; + FPort: Integer; + FUsername: String; + FPassword: String; + FDatabase: WideString; + FOnLog: TMySQLLogEvent; + FOnDatabaseChanged: TMySQLDatabaseChangedEvent; + FOptions: TMySQLClientOptions; + FCapabilities: TMySQLServerCapabilities; + FRowsFound: Int64; + FRowsAffected: Int64; + FServerVersionUntouched: String; + function GetActive: Boolean; + 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; + procedure Log(Category: TMySQLLogCategory; Msg: WideString); + procedure DetectCapabilities; + 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; + property ThreadId: Cardinal read GetThreadId; + property ConnectionStarted: Cardinal read FConnectionStarted; + 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; + published + property Active: Boolean read GetActive 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]; + // 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; + 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 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; +end; + + +destructor TMySQLConnection.Destroy; +begin + if Active then Active := False; + inherited Destroy; +end; + + +{** + (Dis-)Connect to/from server +} +procedure TMySQLConnection.SetActive( Value: Boolean ); +var + Connected: PMYSQL; + ClientFlags: Integer; + Error, tmpdb: WideString; + UsingPass, Protocol: 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'; + Log(lcInfo, 'Characterset: '+CharacterSet); + FConnectionStarted := GetTickCount; + 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'); + end; + +end; + + +function TMySQLConnection.GetActive: Boolean; +begin + if FActive and (mysql_ping(FHandle) <> 0) then + Active := False; + Result := FActive; +end; + + +{** + Executes a query +} +function TMySQLConnection.Query(SQL: WideString; DoStoreResult: Boolean=False): PMYSQL_RES; +var + querystatus: Integer; + NativeSQL: String; +begin + if not Active then + Active := True; + Log(lcSQL, SQL); + NativeSQL := UTF8Encode(SQL); + querystatus := mysql_real_query(FHandle, PChar(NativeSQL), Length(NativeSQL)); + if querystatus <> 0 then begin + Log(lcError, GetLastError); + raise Exception.Create(GetLastError); + end else begin + Result := nil; + // Affected rows are -1 for SELECT queries, 0 for UPDATE queries including + // admin commands e.g. FLUSH PRIVILEGES + FRowsAffected := mysql_affected_rows(FHandle); + if FRowsAffected = -1 then begin + FRowsAffected := 0; + if DoStoreResult then begin + Result := mysql_store_result(FHandle); + FRowsFound := mysql_num_rows(Result); + Log(lcDebug, IntToStr(RowsFound)+' rows found.'); + end; + 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; +begin + Result := WideFormat('SQL Error (%d): %s', [mysql_errno(FHandle), Utf8Decode(mysql_error(FHandle))]); +end; + + +{** + Get version string as normalized integer + "5.1.12-beta-community-123" => 50112 +} +function TMySQLConnection.GetServerVersionInt: Integer; +var + i, dots: Byte; + fullversion, v1, v2, v3: String; +begin + Result := -1; + + dots := 0; + // Avoid calling GetServerVersionUntouched too often + fullversion := ServerVersionUntouched; + v1 := ''; + v2 := ''; + v3 := ''; + for i:=1 to Length(fullversion) do begin + if fullversion[i] = '.' then begin + inc(dots); + // We expect exactly 2 dots. + if dots > 2 then + break; + end else if fullversion[i] in ['0'..'9'] then begin + if dots = 0 then + v1 := v1 + fullversion[i] + else if dots = 1 then + v2 := v2 + fullversion[i] + else if dots = 2 then + v3 := v3 + fullversion[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(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 + try + Results := GetResults(SQL); + Result := TWideStringList.Create; + while not Results.Eof do begin + Result.Add(Results.Col(Column)); + Results.Next; + end; + finally + FreeAndNil(Results); + end; +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 + try + Results := GetResults(SQL); + if Results.RecordCount > 0 then + Result := Results.Col(Column) + else + Result := ''; + finally + FreeAndNil(Results); + end; +end; + + +{** + Get single cell value via SQL query, identified by column name +} +function TMySQLConnection.GetVar(SQL: WideString; Column: WideString): WideString; +var + Results: TMySQLQuery; +begin + try + Results := GetResults(SQL); + if Results.RecordCount > 0 then + Result := Results.Col(Column) + else + Result := ''; + finally + FreeAndNil(Results); + end; +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 + inherited Destroy; + FreeAndNil(FColumnNames); + mysql_free_result(FLastResult); +end; + + +procedure TMySQLQuery.SetSQL(Value: WideString); +begin + FSQL := Value; +end; + + +procedure TMySQLQuery.Execute; +var + i: Integer; + Field: PMYSQL_FIELD; +begin + FLastResult := Connection.Query(FSQL, True); + FRecordCount := Connection.RowsFound; + if HasResult then begin + for i:=0 to mysql_num_fields(FLastResult)-1 do begin + Field := mysql_fetch_field_direct(FLastResult, i); + FColumnNames.Add(Utf8Decode(Field.name)); + end; + RecNo := 0; + end; +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 + FRecNo := Value; + FEof := False; + mysql_data_seek(FLastResult, FRecNo); + 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; +begin + if (Column > -1) and (Column < ColumnCount) then + Result := Utf8Decode(FCurrentRow[Column]) + 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; +var + i: Integer; + Field: PMYSQL_FIELD; +begin + Field := mysql_fetch_field_direct(FLastResult, Column); + Result := Datatypes[Low(Datatypes)]; + for i:=Low(Datatypes) to High(Datatypes) do begin + if Field._type = Datatypes[i].NativeType then begin + Result := Datatypes[i]; + break; + end; + end; +end; + + +function TMySQLQuery.ColExists(Column: WideString): Boolean; +begin + Result := (ColumnNames <> nil) and (ColumnNames.IndexOf(Column) > -1); +end; + + +function TMySQLQuery.IsNull(Column: Integer): Boolean; +begin + Result := FCurrentRow[Column] = nil; +end; + + +function TMySQLQuery.HasResult: Boolean; +begin + Result := FLastResult <> nil; +end; + + +end. diff --git a/source/mysql_structures.pas b/source/mysql_structures.pas index f9506bcf..a3e15eb1 100644 --- a/source/mysql_structures.pas +++ b/source/mysql_structures.pas @@ -7,7 +7,7 @@ unit mysql_structures; interface uses - Classes, Widestrings, Graphics; + Classes, Widestrings, Graphics, mysql_api; {$I const.inc} @@ -28,6 +28,7 @@ type // MySQL data type structure TDatatype = record Index: TDatatypeIndex; + NativeType: Cardinal; // See field types in mysql_api.pas Name: String[18]; Description: String; HasLength: Boolean; // Can have Length- or Set-attribute? @@ -106,6 +107,7 @@ var ( ( Index: dtTinyint; + NativeType: FIELD_TYPE_TINY; Name: 'TINYINT'; Description: 'TINYINT[(M)] [UNSIGNED] [ZEROFILL]' + CRLF + 'A very small integer. The signed range is -128 to 127. ' + @@ -120,6 +122,7 @@ var ), ( Index: dtSmallint; + NativeType: FIELD_TYPE_SHORT; Name: 'SMALLINT'; Description: 'SMALLINT[(M)] [UNSIGNED] [ZEROFILL]' + CRLF + 'A small integer. The signed range is -32768 to 32767. ' + @@ -134,6 +137,7 @@ var ), ( Index: dtMediumint; + NativeType: FIELD_TYPE_INT24; Name: 'MEDIUMINT'; Description: 'MEDIUMINT[(M)] [UNSIGNED] [ZEROFILL]' + CRLF + 'A medium-sized integer. The signed range is -8388608 to 8388607. ' + @@ -148,6 +152,7 @@ var ), ( Index: dtInt; + NativeType: FIELD_TYPE_LONG; Name: 'INT'; Description: 'INT[(M)] [UNSIGNED] [ZEROFILL]' + CRLF + 'A normal-size integer. The signed range is -2147483648 to 2147483647. ' + @@ -162,6 +167,7 @@ var ), ( Index: dtBigint; + NativeType: FIELD_TYPE_LONGLONG; Name: 'BIGINT'; Description: 'BIGINT[(M)] [UNSIGNED] [ZEROFILL]' + CRLF + 'A large integer. The signed range is -9223372036854775808 to ' + @@ -176,6 +182,7 @@ var ), ( Index: dtFloat; + NativeType: FIELD_TYPE_FLOAT; Name: 'FLOAT'; Description: 'FLOAT[(M,D)] [UNSIGNED] [ZEROFILL]' + CRLF + 'A small (single-precision) floating-point number. Allowable values are '+ @@ -193,6 +200,7 @@ var ), ( Index: dtDouble; + NativeType: FIELD_TYPE_DOUBLE; Name: 'DOUBLE'; Description: 'DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]' + CRLF + 'A normal-size (double-precision) floating-point number. Allowable ' + @@ -210,6 +218,7 @@ var ), ( Index: dtDecimal; + NativeType: FIELD_TYPE_DECIMAL; Name: 'DECIMAL'; Description: 'DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]' + CRLF + 'A packed "exact" fixed-point number. M is the total number of digits ' + @@ -229,6 +238,7 @@ var ), ( Index: dtDate; + NativeType: FIELD_TYPE_DATE; Name: 'DATE'; Description: 'DATE' + CRLF + 'A date. The supported range is ''1000-01-01'' to ''9999-12-31''. MySQL ' + @@ -244,6 +254,7 @@ var ), ( Index: dtTime; + NativeType: FIELD_TYPE_TIME; Name: 'TIME'; Description: 'TIME' + CRLF + 'A time. The range is ''-838:59:59'' to ''838:59:59''. MySQL displays TIME ' + @@ -259,6 +270,7 @@ var ), ( Index: dtYear; + NativeType: FIELD_TYPE_YEAR; Name: 'YEAR'; Description: 'YEAR[(2|4)]' + CRLF + 'A year in two-digit or four-digit format. The default is four-digit ' + @@ -277,6 +289,7 @@ var ), ( Index: dtDatetime; + NativeType: FIELD_TYPE_DATETIME; Name: 'DATETIME'; Description: 'DATETIME' + CRLF + 'A date and time combination. The supported range is ''1000-01-01 ' + @@ -293,6 +306,7 @@ var ), ( Index: dtTimestamp; + NativeType: FIELD_TYPE_TIMESTAMP; Name: 'TIMESTAMP'; Description: 'TIMESTAMP' + CRLF + 'A timestamp. The range is ''1970-01-01 00:00:01'' UTC to ''2038-01-09 ' + @@ -311,6 +325,7 @@ var ), ( Index: dtCHAR; + NativeType: FIELD_TYPE_STRING; Name: 'CHAR'; Description: 'CHAR[(M)]' + CRLF + 'A fixed-length string that is always right-padded with spaces to the ' + @@ -329,6 +344,7 @@ var ), ( Index: dtVarchar; + NativeType: FIELD_TYPE_VAR_STRING; Name: 'VARCHAR'; Description: 'VARCHAR(M)' + CRLF + 'A variable-length string. M represents the maximum column length in ' + @@ -351,6 +367,7 @@ var ), ( Index: dtTinytext; + NativeType: FIELD_TYPE_TINY_BLOB; Name: 'TINYTEXT'; Description: 'TINYTEXT' + CRLF + 'A TEXT column with a maximum length of 255 (28 - 1) characters. The ' + @@ -367,6 +384,7 @@ var ), ( Index: dtText; + NativeType: FIELD_TYPE_BLOB; Name: 'TEXT'; Description: 'TEXT[(M)]' + CRLF + 'A TEXT column with a maximum length of 65,535 (216 - 1) characters. The ' + @@ -386,6 +404,7 @@ var ), ( Index: dtMediumtext; + NativeType: FIELD_TYPE_MEDIUM_BLOB; Name: 'MEDIUMTEXT'; Description: 'MEDIUMTEXT' + CRLF + 'A TEXT column with a maximum length of 16,777,215 (224 - 1) characters. ' + @@ -402,6 +421,7 @@ var ), ( Index: dtLongtext; + NativeType: FIELD_TYPE_LONG_BLOB; Name: 'LONGTEXT'; Description: 'LONGTEXT' + CRLF + 'A TEXT column with a maximum length of 4,294,967,295 or 4GB (232 - 1) ' + @@ -421,6 +441,7 @@ var ), ( Index: dtBinary; + NativeType: FIELD_TYPE_STRING; Name: 'BINARY'; Description: 'BINARY(M)' + CRLF + 'The BINARY type is similar to the CHAR type, but stores binary byte ' + @@ -437,6 +458,7 @@ var ), ( Index: dtVarbinary; + NativeType: FIELD_TYPE_VAR_STRING; Name: 'VARBINARY'; Description: 'VARBINARY(M)' + CRLF + 'The VARBINARY type is similar to the VARCHAR type, but stores binary ' + @@ -453,6 +475,7 @@ var ), ( Index: dtTinyblob; + NativeType: FIELD_TYPE_TINY_BLOB; Name: 'TINYBLOB'; Description: 'TINYBLOB' + CRLF + 'A BLOB column with a maximum length of 255 (28 - 1) bytes. Each ' + @@ -468,6 +491,7 @@ var ), ( Index: dtBlob; + NativeType: FIELD_TYPE_BLOB; Name: 'BLOB'; Description: 'BLOB[(M)]' + CRLF + 'A BLOB column with a maximum length of 65,535 (216 - 1) bytes. Each ' + @@ -486,6 +510,7 @@ var ), ( Index: dtMediumblob; + NativeType: FIELD_TYPE_MEDIUM_BLOB; Name: 'MEDIUMBLOB'; Description: 'MEDIUMBLOB' + CRLF + 'A BLOB column with a maximum length of 16,777,215 (224 - 1) bytes. Each ' + @@ -501,6 +526,7 @@ var ), ( Index: dtLongblob; + NativeType: FIELD_TYPE_LONG_BLOB; Name: 'LONGBLOB'; Description: 'LONGBLOB' + CRLF + 'A BLOB column with a maximum length of 4,294,967,295 or 4GB (232 - 1) ' + @@ -518,6 +544,7 @@ var ), ( Index: dtEnum; + NativeType: FIELD_TYPE_ENUM; Name: 'ENUM'; Description: 'ENUM(''value1'',''value2'',...)' + CRLF + 'An enumeration. A string object that can have only one value, chosen ' + @@ -535,6 +562,7 @@ var ), ( Index: dtSet; + NativeType: FIELD_TYPE_SET; Name: 'SET'; Description: 'SET(''value1'',''value2'',...)' + CRLF + 'A set. A string object that can have zero or more values, each of which ' + @@ -552,6 +580,7 @@ var ), ( Index: dtBit; + NativeType: FIELD_TYPE_BIT; Name: 'BIT'; Description: 'BIT[(M)]' + CRLF + 'A bit-field type. M indicates the number of bits per value, from 1 to ' + @@ -566,6 +595,7 @@ var ), ( Index: dtPoint; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'POINT'; Description: 'POINT(x,y)' + CRLF + 'Constructs a WKB Point using its coordinates.'; @@ -579,6 +609,7 @@ var ), ( Index: dtLinestring; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'LINESTRING'; Description: 'LINESTRING(pt1,pt2,...)' + CRLF + 'Constructs a WKB LineString value from a number of WKB Point arguments. ' + @@ -594,6 +625,7 @@ var ), ( Index: dtPolygon; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'POLYGON'; Description: 'POLYGON(ls1,ls2,...)' + CRLF + 'Constructs a WKB Polygon value from a number of WKB LineString ' + @@ -609,6 +641,7 @@ var ), ( Index: dtGeometry; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'GEOMETRY'; Description: ''; HasLength: False; @@ -621,6 +654,7 @@ var ), ( Index: dtMultipoint; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'MULTIPOINT'; Description: 'MULTIPOINT(pt1,pt2,...)' + CRLF + 'Constructs a WKB MultiPoint value using WKB Point arguments. If any ' + @@ -635,6 +669,7 @@ var ), ( Index: dtMultilinestring; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'MULTILINESTRING'; Description: 'MULTILINESTRING(ls1,ls2,...)' + CRLF + 'Constructs a WKB MultiLineString value using WKB LineString arguments. ' + @@ -649,6 +684,7 @@ var ), ( Index: dtMultipolygon; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'MULTIPOLYGON'; Description: 'MULTIPOLYGON(poly1,poly2,...)' + CRLF + 'Constructs a WKB MultiPolygon value from a set of WKB Polygon ' + @@ -664,6 +700,7 @@ var ), ( Index: dtGeometrycollection; + NativeType: FIELD_TYPE_GEOMETRY; Name: 'GEOMETRYCOLLECTION'; Description: 'GEOMETRYCOLLECTION(g1,g2,...)' + CRLF + 'Constructs a WKB GeometryCollection. If any argument is not a ' + diff --git a/source/mysqlconn.pas b/source/mysqlconn.pas deleted file mode 100644 index 0e838481..00000000 --- a/source/mysqlconn.pas +++ /dev/null @@ -1,117 +0,0 @@ -unit MysqlConn; - -interface - -uses ZConnection, ExtCtrls, MysqlQueryThread; - -const - // connection attemp result codes - MCR_SUCCESS = 0; - MCR_FAILED = 1; - -{$I const.inc} - -type - - - TMysqlConn = class - private - FConn : TZConnection; - FOpenConn : TOpenConnProf; - FLastError : String; - function GetIsAlive: Boolean; - function GetIsConnected: Boolean; - //FTimer : TTimer; - public - constructor Create(AConn : POpenConnProf); - destructor Destroy(); override; - function Connect() : Integer; - procedure Disconnect(); - property IsConnected : Boolean read GetIsConnected; - property IsAlive : Boolean read GetIsAlive; - property Connection : TZConnection read FConn; - property LastError : String read FLastError; - end; - -implementation - -uses SysUtils; - -{ TMysqlConn } - -constructor TMysqlConn.Create; -begin - FConn := TZConnection.Create(nil); - FOpenConn := AConn^; - FLastError := ''; -end; - -function TMysqlConn.Connect(): Integer; -begin - FLastError := ''; - - if FConn.Connected then FConn.Disconnect; - with FOpenConn.MysqlParams do - begin - FConn.Protocol := 'mysql'; - if FOpenConn.MysqlParams.NetType = NETTYPE_TCPIP then begin - FConn.Hostname := Host; - FConn.SocketName := ''; - end else begin - FConn.Hostname := '.'; - FConn.SocketName := Host; - end; - FConn.User := User; - FConn.Password := Pass; - FConn.Port := Port; - - FConn.Properties.Values['compress'] := PrpCompress; - FConn.Properties.Values['dbless'] := PrpDbless; - FConn.Properties.Values['CLIENT_LOCAL_FILES'] := PrpClientLocalFiles; - FConn.Properties.Values['CLIENT_INTERACTIVE'] := PrpClientInteractive; - // ZConn.Properties.Values['USE_RESULT'] := 'true'; // doesn't work - // ZConn.Properties.Values['CLIENT_SSL'] := 'true'; // from an mdaems's example - FConn.Properties.Values['CLIENT_MULTI_RESULTS'] := '1'; - end; - - try - FConn.Connect(); - Result := MCR_SUCCESS; - except - // todo: handle exception - on E : Exception do - begin - FLastError := E.Message; - Result := MCR_FAILED; - end; - end; -end; - - -procedure TMysqlConn.Disconnect; -begin - if FConn.Connected then FConn.Disconnect; -end; - - -destructor TMysqlConn.Destroy; -begin - if FConn.Connected then FConn.Disconnect; - FreeAndNil (FConn); - inherited; -end; - -function TMysqlConn.GetIsAlive: Boolean; -begin - Result := False; - if IsConnected then Result := FConn.Ping(); -end; - -function TMysqlConn.GetIsConnected: Boolean; -begin - Result := FConn.Connected; -end; - - - -end. diff --git a/source/mysqlquery.pas b/source/mysqlquery.pas deleted file mode 100644 index 786c542e..00000000 --- a/source/mysqlquery.pas +++ /dev/null @@ -1,201 +0,0 @@ -unit MysqlQuery; - -interface - -uses Windows, Messages, Classes, Db, ZConnection, ZDataSet, MysqlQueryThread, helpers; - -const - // Thread notification events - MQE_INITED = 0; // initialized - MQE_STARTED = 1; // query started - MQE_FINISHED = 2; // query finished - MQE_FREED = 3; // object removed from memory - - // Query result codes - MQR_NOTHING = 0; // no result yet - MQR_SUCCESS = 1; // success - MQR_CONNECT_FAIL = 2; // done with error - MQR_QUERY_FAIL = 3; // done with error - -{$I const.inc} - -type - TMysqlQuery = class; - - TMysqlQueryNotificationEvent = procedure (ASender : TMysqlQuery; AEvent : Integer) of object; - - TMysqlQuery = class - private - FConn : TOpenConnProf; - FQueryResult : TThreadResult; - FMysqlConnection : TZConnection; - FMysqlConnectionIsOwned : Boolean; - FMysqlDataset : TDataset; - FThreadID : Integer; - FQueryThread : TMysqlQueryThread; - FEventName : String; - FEventHandle : THandle; - FSql : WideString; - function GetComment: String; - function GetResult: Integer; - function GetHasresultSet: Boolean; - protected - - public - constructor Create (AOwner : TComponent; AConn : POpenConnProf); overload; - destructor Destroy (); override; - procedure Query(ASql: WideString; ANotifyWndHandle : THandle; Callback: TAsyncPostRunner; ds: TDeferDataSet); - procedure SetMysqlDataset(ADataset : TDataset); - procedure PostNotification (AQueryResult : TThreadResult; AEvent : Integer); - procedure SetThreadResult(AResult : TThreadResult); - - property Result : Integer read GetResult; // Query result code - property Comment : String read GetComment; // Textual information about the query result, includes error description - property MysqlConnection : TZConnection read FMysqlConnection; - property MysqlDataset : TDataset read FMysqlDataset; // Resultset - property HasResultset : Boolean read GetHasresultSet; // Indicator of resultset availability - property ThreadID : Integer read FThreadID; // Mysql query thread ID (on the clients os) - property Sql : WideString read FSql; // Query string - property EventName : String read FEventName; // Operating system event name used for blocking mode - property EventHandle : THandle read FEventHandle; - end; - - function ExecMysqlStatementAsync(ASql : WideString; AConn : TOpenConnProf; AWndHandle : THandle; Callback: TAsyncPostRunner) : TMysqlQuery; - function ExecPostAsync(AConn : TOpenConnProf; AWndHandle : THandle; ds: TDeferDataSet): TMysqlQuery; - - -implementation - -uses - SysUtils, - Dialogs; - - -{*** - Wrapper function to simplify running a query in asynchronous mode - This function will end right after the thread is created. - - Otherwise status notifications are sent by the WM_MYSQL_THREAD_NOTIFY message; - * use the WParam member of the AMessage parameter (a TMysqlQuery object) - - @param string SQL-statement - @param TConnParams Connection credentials structure - @param THandle Window handle to post thread status messages to -} -function ExecMysqlStatementAsync(ASql : WideString; AConn : TOpenConnProf; AWndHandle : THandle; Callback: TAsyncPostRunner) : TMysqlQuery; -begin - Result := TMysqlQuery.Create(nil,@AConn); - Result.Query(ASql,AWndHandle,Callback,nil); -end; - - -function ExecPostAsync(AConn : TOpenConnProf; AWndHandle : THandle; ds: TDeferDataSet): TMysqlQuery; -begin - Result := TMysqlQuery.Create(nil,@AConn); - Result.Query('',AWndHandle,nil,ds); -end; - - -{ TMysqlQuery } - -{*** - Constructor - - @param TComponent Owner - @param PConnParams Used to pass connection credentials. If the MysqlConn member - (a TZConnection object) <>nil, the query will be run on this connection. - Otherwise a new connection object is created with the credentials - in the MysqlParams member -} - -constructor TMysqlQuery.Create(AOwner: TComponent; AConn: POpenConnProf); -begin - FConn := AConn^; - FMysqlConnectionIsOwned := False; - - ZeroMemory (@FQueryResult,SizeOf(FQueryResult)); - FSql := ''; - - if AConn.MysqlConn<>nil then - FMysqlConnection := AConn.MysqlConn - else - begin - FMysqlConnectionIsOwned := True; - FMysqlConnection := TZConnection.Create(nil); - end; - - FMysqlDataset := nil; -end; - - -{*** - Destructor: - remove created objects from memory - - @result -} - -destructor TMysqlQuery.Destroy; -begin - //FreeAndNil (FMysqlDataset); - - // Only free the connection object if we first created it - if FMysqlConnectionIsOwned then - FreeAndNil (FMysqlConnection); - - inherited; -end; - -function TMysqlQuery.GetComment: String; -begin - Result := FQueryResult.Comment; -end; - -function TMysqlQuery.GetHasresultSet: Boolean; -begin - Result := FMysqlDataset <> nil; -end; - -function TMysqlQuery.GetResult: Integer; -begin - Result := FQueryResult.Result; -end; - -procedure TMysqlQuery.PostNotification(AQueryResult: TThreadResult; AEvent : Integer); -begin - SetThreadResult(AQueryResult); - debug(Format('qry: Not calling notify function, event type %d occurred.', [AEvent])); -end; - -procedure TMysqlQuery.Query(ASql: WideString; ANotifyWndHandle : THandle; Callback: TAsyncPostRunner; ds: TDeferDataSet); -begin - // create thread object - FQueryThread := TMysqlQueryThread.Create(Self,FConn,ASql,Callback,ds); - FQueryThread.NotifyWndHandle := ANotifyWndHandle; - FThreadID := FQueryThread.ThreadID; - FEventName := APPNAME+'_'+IntToStr(FThreadID); - FSql := ASql; - - FEventHandle := CreateEvent ({*EVENT_MODIFY_STATE + SYNCHRONIZE*}nil, False, False, PChar(FEventName)); - - // exec query - debug(Format('qry: Starting query thread %d', [FQueryThread.ThreadID])); - FQueryThread.Resume(); -end; - - -procedure TMysqlQuery.SetMysqlDataset(ADataset: TDataset); -begin - FMysqlDataset := ADataset; -end; - -procedure TMysqlQuery.SetThreadResult(AResult: TThreadResult); -begin - try - FQueryResult := AResult; - except - raise Exception.Create('Assertion failed: Internal error in SetThreadResult().'); - end; -end; - -end. diff --git a/source/mysqlquerythread.pas b/source/mysqlquerythread.pas deleted file mode 100644 index 6a89c40b..00000000 --- a/source/mysqlquerythread.pas +++ /dev/null @@ -1,333 +0,0 @@ -unit MysqlQueryThread; - -interface - -uses - Windows, Messages, Forms, Db, Classes, ZConnection, ZDataSet, StdCtrls, SysUtils, - ZMessages, - helpers, SynRegExpr, mysql_structures; - -{$IFDEF EXAMPLE_APP} -const - WM_MYSQL_THREAD_NOTIFY = WM_USER+100; -{$ENDIF} - -type - // Exception information - TExceptionData = record - Msg : String[200]; - HelpContext : Integer; - end; - - // Mysql protocol-relevant connection parameter structure - TMysqlConnParams = record - NetType: Integer; - Host: String; - Database: WideString; - Protocol, - User, - Pass : String; - Port : Integer; - PrpCompress, - PrpDbless, - PrpClientLocalFiles, - PrpClientInteractive : String; - end; - PMysqlConnParams = ^TMysqlConnParams; - - // Established connection and it's corresponding connection profile. - // (The actual connection need not necessarily be open, of course, it could in theory be closed or nil, - // but to keep the name short; this beats "TConnectionProfileDataAndConnectionObject", which I guess would be the proper name. - TOpenConnProf = record - MysqlParams : TMysqlConnParams; // stuff that needs to be shipped over to the mysql driver. - MysqlConn : TZConnection; - end; - POpenConnProf = ^TOpenConnProf; - - TThreadResult = record - ThreadID : Integer; - Action : Integer; - Sql : WideString; - Result : Integer; - Comment : String; - end; - - TMysqlQueryThread = class(TThread) - private - FMysqlConn : TZConnection; - FConn : TOpenConnProf; - FOwner : TObject; // TMysqlQuery object - FSql : WideString; - FCallback: TAsyncPostRunner; - FPostDataSet: TDeferDataSet; - FResult : Integer; - FComment : String; - FNotifyWndHandle : THandle; - function GetExceptionData(AException : Exception) : TExceptionData; - protected - procedure Execute; override; - procedure SetState (AResult : Integer; AComment : String); - procedure SetNotifyWndHandle (Value : THandle); - procedure NotifyStatus (AEvent : Integer); - procedure NotifyStatusViaWinMessage (AEvent : Integer); - function AssembleResult () : TThreadResult; - function RunDataQuery (ASql : WideString; var ADataset : TDataset; out AExceptionData : TExceptionData; callback: TAsyncPostRunner) : Boolean; - function RunUpdateQuery (ASql : WideString; var ADataset : TDataset; out AExceptionData : TExceptionData; callback: TAsyncPostRunner) : Boolean; - function QuerySingleCellAsInteger (ASql : WideString) : Integer; - public - constructor Create (AOwner : TObject; AConn : TOpenConnProf; ASql : WideString; Callback: TAsyncPostRunner; APostDataSet: TDeferDataSet); - destructor Destroy; override; - property NotifyWndHandle : THandle read FNotifyWndHandle write SetNotifyWndHandle; - end; - -implementation - -uses - MysqlQuery, Dialogs, communication -{$IFNDEF EXAMPLE_APP} -, Main -{$ENDIF} - ; - -function TMysqlQueryThread.AssembleResult: TThreadResult; -begin - ZeroMemory (@Result,SizeOf(Result)); - - Result.ThreadID := ThreadID; - Result.Action := 1; - Result.Sql := FSql; - Result.Result := FResult; - Result.Comment := FComment; -end; - -constructor TMysqlQueryThread.Create (AOwner : TObject; AConn : TOpenConnProf; ASql : WideString; Callback: TAsyncPostRunner; APostDataSet: TDeferDataSet); -var - mc : TZConnection; -begin - Inherited Create(True); - - FOwner := AOwner; - FConn := AConn; - FCallback := Callback; - FPostDataSet := APostDataSet; - mc := TMysqlQuery(FOwner).MysqlConnection; - FMysqlConn := mc; - FResult := 0; - FSql := ASql; - - if AConn.MysqlParams.NetType = NETTYPE_TCPIP then begin - mc.HostName := AConn.MysqlParams.Host; - mc.SocketName := ''; - end else begin - mc.HostName := '.'; - mc.SocketName := AConn.MysqlParams.Host; - end; - mc.Database := AConn.MysqlParams.Database; - mc.User := AConn.MysqlParams.User; - mc.Password := AConn.MysqlParams.Pass; - mc.Protocol := AConn.MysqlParams.Protocol; - mc.Port := AConn.MysqlParams.Port; - - FreeOnTerminate := True; -end; - -destructor TMysqlQueryThread.Destroy; -begin - inherited; -end; - - -procedure TMysqlQueryThread.NotifyStatus(AEvent: Integer); -var - h : THandle; - qr : TThreadResult; -begin - if AEvent = MQE_FINISHED then begin - debug(Format('qry: Setting result', [AEvent])); - qr := AssembleResult(); - TMysqlQuery(FOwner).SetThreadResult(qr); - // trigger query finished event - h := OpenEvent (EVENT_MODIFY_STATE,False,PChar(TMysqlQuery(FOwner).EventName)); - debug('qry: Signalling completion via event.'); - if not SetEvent (h) then raise Exception.Create(Format('Assertion failed: Error %d signaling event', [GetLastError])); - CloseHandle(h); - end; - NotifyStatusViaWinMessage(AEvent); -end; - -procedure TMysqlQueryThread.NotifyStatusViaWinMessage(AEvent: Integer); -begin - debug(Format('qry: Posting status %d via WM_MYSQL_THREAD_NOTIFY message', [AEvent])); - PostMessage(FNotifyWndHandle,WM_MYSQL_THREAD_NOTIFY,Integer(FOwner),AEvent); -end; - -procedure TMysqlQueryThread.Execute; -var - q : TDeferDataSet; - r : Boolean; - ex : TExceptionData; -begin - debug(Format('qry: Thread %d running...', [ThreadID])); - NotifyStatus(MQE_INITED); - - try - if not FMysqlConn.Connected then FMysqlConn.Connect(); - except - on E: Exception do begin - SetState (MQR_CONNECT_FAIL,Format('%s',[E.Message])); - end; - end; - - if FMysqlConn.Connected then begin - NotifyStatus (MQE_STARTED); - - q := nil; - if FPostDataSet <> nil then begin - try - FPostDataSet.DoAsync; - SetState (MQR_SUCCESS,'SUCCESS') - except - on E: Exception do begin - SetState (MQR_QUERY_FAIL,Format('%s', [E.Message])); - end; - end; - end else begin - try - r := RunDataQuery (FSql,TDataSet(q),ex,FCallback); - TMysqlQuery(FOwner).SetMysqlDataset(q); - - if r then SetState (MQR_SUCCESS,'SUCCESS') - else SetState (MQR_QUERY_FAIL,ex.Msg); - except - on E: Exception do begin - SetState (MQR_QUERY_FAIL,Format('%s', [E.Message])); - end; - end; - end; - end; - - NotifyStatus (MQE_FINISHED); - NotifyStatus (MQE_FREED); - debug(Format('qry: Thread %d suspending.', [ThreadID])); -end; - - -function TMysqlQueryThread.GetExceptionData( - AException: Exception): TExceptionData; -begin - ZeroMemory (@Result,SizeOf(Result)); - Result.Msg := AException.Message; - Result.HelpContext := AException.HelpContext; -end; - -function TMysqlQueryThread.QuerySingleCellAsInteger(ASql: WideString): Integer; -var - ds : TDataSet; - e : TExceptionData; -begin - Result := 0; - - if RunDataQuery(ASql,ds,e, FCallback) then - begin - if ds.Fields.Count > 0 then - Result := ds.Fields[0].AsInteger; - FreeAndNil (ds); - end; -end; - -function TMysqlQueryThread.RunDataQuery(ASql: WideString; - var ADataset: TDataset; out AExceptionData : TExceptionData; callback: TAsyncPostRunner): Boolean; -var - q : TDeferDataSet; -begin - Result := False; - q := TDeferDataSet.Create(nil, callback); - q.Connection := FMysqlConn; - // Parameter checking is used only in Insert Files, which has it's own TZQuery. - q.ParamCheck := false; - q.SQL.Clear; - q.SQL.Add(ASql); - ADataset := q; - - try - q.Active := True; - Result := True; - except - on E: Exception do begin - if E.Message = SCanNotOpenResultSet then begin - Result := true; - FreeAndNil(ADataset); - end else if MainForm.cancelling then begin - AExceptionData := GetExceptionData(Exception.Create('Cancelled by user.')); - try - FMysqlConn.Reconnect; - except - end; - MainForm.cancelling := false; - end else begin - AExceptionData := GetExceptionData(E); - end; - end; - end; -end; - -function TMysqlQueryThread.RunUpdateQuery(ASql: WideString; var ADataset: TDataset; out AExceptionData : TExceptionData; callback: TAsyncPostRunner): Boolean; -var - q : TDeferDataSet; -begin - Result := False; - q := TDeferDataSet.Create(nil, callback); - q.Connection := FMysqlConn; - // Parameter checking is used only in Insert Files, which has it's own TZQuery. - q.ParamCheck := false; - q.SQL.Text := ASql; - ADataSet := q; - - try - q.DoAsyncExecSql(); - Result := True; - except - On E: Exception do begin - if MainForm.cancelling then begin - AExceptionData := GetExceptionData(Exception.Create('Cancelled by user.')); - try - FMysqlConn.Reconnect; - except - end; - MainForm.cancelling := false; - end else begin - AExceptionData := GetExceptionData(E); - end; - end; - end; - - FreeAndNil (q); -end; - -procedure TMysqlQueryThread.SetNotifyWndHandle(Value: THandle); -begin - FNotifyWndHandle := Value; -end; - -procedure TMysqlQueryThread.SetState(AResult: Integer; AComment: String); -var - rx: TRegExpr; - msg: String; -begin - debug(Format('qry: Setting status %d with comment %s', [AResult, AComment])); - FResult := AResult; - FComment := AComment; - if FResult <> MQR_SUCCESS then begin - // Find "(errno: 123)" in message and add more meaningful message from perror.exe - rx := TRegExpr.Create; - rx.Expression := '.+\(errno\:\s+(\d+)\)'; - if rx.Exec(FComment) then begin - msg := MySQLErrorCodes.Values[rx.Match[1]]; - if msg <> '' then - FComment := FComment + CRLF + CRLF + msg; - end; - rx.Free; - end; -end; - -end. diff --git a/source/queryprogress.dfm b/source/queryprogress.dfm deleted file mode 100644 index 5b963998..00000000 --- a/source/queryprogress.dfm +++ /dev/null @@ -1,39 +0,0 @@ -object frmQueryProgress: TfrmQueryProgress - Left = 0 - Top = 0 - BorderIcons = [] - BorderStyle = bsDialog - Caption = 'Status' - ClientHeight = 78 - ClientWidth = 222 - Color = clBtnFace - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [] - OldCreateOrder = False - Position = poScreenCenter - OnClose = FormClose - OnCreate = FormCreate - PixelsPerInch = 96 - TextHeight = 13 - object lblStatusMsg: TLabel - Left = 24 - Top = 16 - Width = 158 - Height = 13 - Caption = 'Waiting for query to complete ...' - end - object btnAbort: TButton - Left = 72 - Top = 44 - Width = 73 - Height = 25 - Cancel = True - Caption = 'Cancel' - Default = True - TabOrder = 0 - OnClick = btnAbortClick - end -end diff --git a/source/queryprogress.pas b/source/queryprogress.pas deleted file mode 100644 index 9f04b978..00000000 --- a/source/queryprogress.pas +++ /dev/null @@ -1,76 +0,0 @@ -unit queryprogress; - -interface - -uses - Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, - Dialogs, StdCtrls,MysqlQuery,MysqlQueryThread, ExtCtrls, communication; - -type - TfrmQueryProgress = class(TForm) - btnAbort: TButton; - lblStatusMsg: TLabel; - procedure FormClose(Sender: TObject; var Action: TCloseAction); - procedure btnAbortClick(Sender: TObject); - procedure FormCreate(Sender: TObject); - private - procedure HandleQueryNotificationMsg(var AMessage : TMessage); message WM_MYSQL_THREAD_NOTIFY; - public - - end; - -var - frmQueryProgress: TfrmQueryProgress; - -implementation - -uses - helpers, - main; - -{$R *.dfm} - -procedure TfrmQueryProgress.btnAbortClick(Sender: TObject); -begin - MainForm.CancelQuery; -end; - - -procedure TfrmQueryProgress.FormClose(Sender: TObject; - var Action: TCloseAction); -begin - Action := caFree; -end; - -procedure TfrmQueryProgress.FormCreate(Sender: TObject); -begin - InheritFont(Font); -end; - -{*** - Handles the TMysqlQueryThread notification messages. - - @param TMessage Message structure containing - * LParam: Event type - * WParam: MysqlQuery object containing status + resultset -} - -procedure TfrmQueryProgress.HandleQueryNotificationMsg(var AMessage: TMessage); -begin - debug(Format('qry: Progress form received WM_MYSQL_THREAD_NOTIFY message with status %d', [AMessage.LParam])); - case AMessage.LParam of - MQE_INITED: - begin - debug('qry: Setting running flag to ''true''.'); - end; - MQE_FINISHED: - begin - debug('qry: Setting running flag to ''false'' and closing dialog.'); - Close(); - end; - end; -end; - - - -end. diff --git a/source/routine_editor.pas b/source/routine_editor.pas index da71a013..6f6201ce 100644 --- a/source/routine_editor.pas +++ b/source/routine_editor.pas @@ -5,7 +5,7 @@ interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, SynEdit, SynMemo, StdCtrls, TntStdCtrls, ComCtrls, ToolWin, - VirtualTrees, WideStrings, db, SynRegExpr, WideStrUtils; + VirtualTrees, WideStrings, mysql_connection, SynRegExpr, WideStrUtils; type TfrmRoutineEditor = class(TFrame) @@ -116,7 +116,7 @@ end; procedure TfrmRoutineEditor.Init(AlterRoutineName: WideString=''; AlterRoutineType: String=''); var - ds: TDataSet; + Results: TMySQLQuery; Create, Params: WideString; ParenthesesCount: Integer; Context: String; @@ -138,22 +138,21 @@ begin if FAlterRoutineName <> '' then begin // Editing existing routine Mainform.SetEditorTabCaption(Self, FAlterRoutineName); - ds := Mainform.GetResults('SELECT * FROM '+DBNAME_INFORMATION_SCHEMA+'.ROUTINES'+ + Results := Mainform.Connection.GetResults('SELECT * FROM '+DBNAME_INFORMATION_SCHEMA+'.ROUTINES'+ ' WHERE ROUTINE_SCHEMA='+esc(Mainform.ActiveDatabase)+ ' AND ROUTINE_NAME='+esc(FAlterRoutineName)+ ' AND ROUTINE_TYPE='+esc(FAlterRoutineType) ); - if ds.RecordCount <> 1 then + if Results.RecordCount <> 1 then Exception.Create('Cannot find properties of stored routine '+FAlterRoutineName); - ds.First; comboType.ItemIndex := ListIndexByRegExpr(comboType.Items, '^'+FAlterRoutineType+'\b'); - chkDeterministic.Checked := ds.FieldByName('IS_DETERMINISTIC').AsString = 'YES'; - comboReturns.Text := ds.FieldByName('DTD_IDENTIFIER').AsWideString; - comboDataAccess.ItemIndex := comboDataAccess.Items.IndexOf(ds.FieldByName('SQL_DATA_ACCESS').AsString); - comboSecurity.ItemIndex := comboSecurity.Items.IndexOf(ds.FieldByName('SECURITY_TYPE').AsString); - editComment.Text := ds.FieldByName('ROUTINE_COMMENT').AsWideString; - SynMemoBody.Text := ds.FieldByName('ROUTINE_DEFINITION').AsWideString; - Create := Mainform.GetVar('SHOW CREATE '+FAlterRoutineType+' '+Mainform.mask(editName.Text), 2); + chkDeterministic.Checked := Results.Col('IS_DETERMINISTIC') = 'YES'; + comboReturns.Text := Results.Col('DTD_IDENTIFIER'); + comboDataAccess.ItemIndex := comboDataAccess.Items.IndexOf(Results.Col('SQL_DATA_ACCESS')); + comboSecurity.ItemIndex := comboSecurity.Items.IndexOf(Results.Col('SECURITY_TYPE')); + editComment.Text := Results.Col('ROUTINE_COMMENT'); + SynMemoBody.Text := Results.Col('ROUTINE_DEFINITION'); + Create := Mainform.Connection.GetVar('SHOW CREATE '+FAlterRoutineType+' '+Mainform.mask(editName.Text), 2); rx := TRegExpr.Create; rx.ModifierI := True; rx.ModifierG := True; @@ -181,7 +180,7 @@ begin if not rx.ExecNext then break; end; - FreeAndNil(ds); + FreeAndNil(Results); end else Mainform.SetEditorTabCaption(Self, ''); editNameChange(Self); @@ -443,7 +442,7 @@ begin if FAlterRoutineName <> '' then begin // Create temp name i := 0; - allRoutineNames := Mainform.GetCol('SELECT ROUTINE_NAME FROM '+Mainform.mask(DBNAME_INFORMATION_SCHEMA)+'.'+Mainform.mask('ROUTINES')+ + allRoutineNames := Mainform.Connection.GetCol('SELECT ROUTINE_NAME FROM '+Mainform.mask(DBNAME_INFORMATION_SCHEMA)+'.'+Mainform.mask('ROUTINES')+ ' WHERE ROUTINE_SCHEMA = '+esc(Mainform.ActiveDatabase)+ ' AND ROUTINE_TYPE = '+esc(ProcOrFunc) ); @@ -462,18 +461,18 @@ begin break; end; TempSQL := 'CREATE '+ProcOrFunc+' '+Mainform.mask(tempName)+'(' + BaseSQL; - Mainform.ExecUpdateQuery(TempSQL, False, True); + Mainform.Connection.Query(TempSQL); // Drop temporary routine, used for syntax checking - Mainform.ExecUpdateQuery('DROP '+ProcOrFunc+' IF EXISTS '+Mainform.mask(TempName)); + Mainform.Connection.Query('DROP '+ProcOrFunc+' IF EXISTS '+Mainform.mask(TempName)); // Drop edited routine - Mainform.ExecUpdateQuery('DROP '+FAlterRoutineType+' IF EXISTS '+Mainform.mask(FAlterRoutineName)); + Mainform.Connection.Query('DROP '+FAlterRoutineType+' IF EXISTS '+Mainform.mask(FAlterRoutineName)); if TargetExists then begin // Drop target routine - overwriting has been confirmed, see above - Mainform.ExecUpdateQuery('DROP '+ProcOrFunc+' IF EXISTS '+Mainform.mask(editName.Text)); + Mainform.Connection.Query('DROP '+ProcOrFunc+' IF EXISTS '+Mainform.mask(editName.Text)); end; end; FinalSQL := 'CREATE '+ProcOrFunc+' '+Mainform.mask(editName.Text)+'(' + BaseSQL; - Mainform.ExecUpdateQuery(FinalSQL, False, True); + Mainform.Connection.Query(FinalSQL); // Set editing name if create/alter query was successful FAlterRoutineName := editName.Text; FAlterRoutineType := ProcOrFunc; diff --git a/source/runsqlfile.pas b/source/runsqlfile.pas index 540f9a6f..5f5d1dcf 100644 --- a/source/runsqlfile.pas +++ b/source/runsqlfile.pas @@ -143,7 +143,8 @@ begin lblTimeValue.Caption := FormatTimeNumber( (GetTickCount - starttime) DIV 1000 ); // Execute single query and display affected rows - rowsaffected := rowsaffected + Mainform.ExecUpdateQuery( SQL[i], True, False ); + Mainform.Connection.Query(SQL[i]); + rowsaffected := rowsaffected + Mainform.Connection.RowsAffected; lblAffectedRowsValue.Caption := FormatNumber( rowsaffected ); Repaint; diff --git a/source/selectdbobject.pas b/source/selectdbobject.pas index b43f64c0..37c8f571 100644 --- a/source/selectdbobject.pas +++ b/source/selectdbobject.pas @@ -4,7 +4,7 @@ interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, - Dialogs, StdCtrls, VirtualTrees, DB, WideStrings, + Dialogs, StdCtrls, VirtualTrees, mysql_connection, WideStrings, TntStdCtrls; type @@ -163,14 +163,14 @@ procedure TfrmSelectDBObject.TreeDBOGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); var - ds: TDataset; + Results: TMySQLQuery; begin case Sender.GetNodeLevel(Node) of 0: ImageIndex := ICONINDEX_DB; 1: begin - ds := Mainform.FetchDbTableList(Mainform.Databases[Node.Parent.Index]); - ds.RecNo := Node.Index+1; - case GetDBObjectType(ds.Fields) of + Results := Mainform.FetchDbTableList(Mainform.Databases[Node.Parent.Index]); + Results.RecNo := Node.Index; + case GetDBObjectType(Results) of lntCrashedTable: ImageIndex := ICONINDEX_CRASHED_TABLE; lntTable: ImageIndex := ICONINDEX_TABLE; lntView: ImageIndex := ICONINDEX_VIEW; @@ -191,22 +191,22 @@ end; procedure TfrmSelectDBObject.TreeDBOInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal); var - ds: TDataset; + Results: TMySQLQuery; cols: TWideStringList; begin // Fetch sub nodes case Sender.GetNodeLevel(Node) of 0: begin // DB expanding - ds := Mainform.FetchDbTableList(Mainform.Databases[Node.Index]); - ChildCount := ds.RecordCount; - SetLength(FColumns[Node.Index], ds.RecordCount); + Results := Mainform.FetchDbTableList(Mainform.Databases[Node.Index]); + ChildCount := Results.RecordCount; + SetLength(FColumns[Node.Index], Results.RecordCount); end; 1: begin // Table expanding - ds := Mainform.FetchDbTableList(Mainform.Databases[Node.Parent.Index]); - ds.RecNo := Node.Index+1; - cols := Mainform.GetCol('SHOW COLUMNS FROM ' + Results := Mainform.FetchDbTableList(Mainform.Databases[Node.Parent.Index]); + Results.RecNo := Node.Index; + cols := Mainform.Connection.GetCol('SHOW COLUMNS FROM ' + Mainform.mask(Mainform.Databases[Node.Parent.Index])+'.' - + Mainform.Mask(ds.FieldByName(DBO_NAME).AsWideString)); + + Mainform.Mask(Results.Col(DBO_NAME))); FColumns[Node.Parent.Index][Node.Index] := cols; ChildCount := cols.Count; end; @@ -219,14 +219,14 @@ procedure TfrmSelectDBObject.TreeDBOGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString); var - ds: TDataset; + Results: TMySQLQuery; begin case Sender.GetNodeLevel(Node) of 0: CellText := Mainform.Databases[Node.Index]; 1: begin - ds := Mainform.FetchDbTableList(Mainform.Databases[Node.Parent.Index]); - ds.RecNo := Node.Index+1; - CellText := ds.FieldByName(DBO_NAME).AsWideString; + Results := Mainform.FetchDbTableList(Mainform.Databases[Node.Parent.Index]); + Results.RecNo := Node.Index; + CellText := Results.Col(DBO_NAME); end; 2: CellText := FColumns[Node.Parent.Parent.Index][Node.Parent.Index][Node.Index]; end; diff --git a/source/sqlhelp.pas b/source/sqlhelp.pas index f1ce7ff8..88ba7520 100644 --- a/source/sqlhelp.pas +++ b/source/sqlhelp.pas @@ -6,7 +6,7 @@ uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ExtCtrls, ShellApi, Buttons, PngSpeedButton, SynMemo, SynEditHighlighter, SynHighlighterURI, - SynURIOpener, SynEdit; + SynURIOpener, SynEdit, mysql_connection; type TfrmSQLhelp = class(TForm) @@ -57,7 +57,7 @@ type implementation -uses ZDataset, helpers, main, db; +uses helpers, main, db; {$I const.inc} @@ -107,46 +107,36 @@ end; } procedure TfrmSQLhelp.fillTreeLevel( ParentNode: TTreeNode ); var - tnode : TTreeNode; - i : integer; - ds : TDataSet; - topic : String; + tnode: TTreeNode; + Results: TMySQLQuery; + topic: String; begin - if ParentNode = nil then - begin + if ParentNode = nil then begin treeTopics.Items.Clear; topic := 'CONTENTS'; - end - else - begin + end else begin ParentNode.DeleteChildren; topic := ParentNode.Text; end; - ds := nil; try Screen.Cursor := crHourglass; - ds := Mainform.GetResults( 'HELP "'+topic+'"' ); - for i:=1 to ds.RecordCount do - begin - tnode := treeTopics.Items.AddChild( ParentNode, ds.FieldByName('name').AsString ); - if (ds.FindField('is_it_category') <> nil) and (ds.FieldByName('is_it_category').AsString = 'Y') then - begin + Results := Mainform.Connection.GetResults( 'HELP "'+topic+'"' ); + while not Results.Eof do begin + tnode := treeTopics.Items.AddChild( ParentNode, Results.Col('name')); + if Results.ColExists('is_it_category') and (Results.Col('is_it_category') = 'Y') then begin tnode.ImageIndex := ICONINDEX_CATEGORY_CLOSED; tnode.SelectedIndex := ICONINDEX_CATEGORY_OPENED; // Add a dummy item to show the plus-button so the user sees that there this // is a category. When the plus-button is clicked, fetch the content of the category treeTopics.Items.AddChild( tnode, DUMMY_NODE_TEXT ); - end - else - begin + end else begin tnode.ImageIndex := ICONINDEX_HELPITEM; tnode.SelectedIndex := tnode.ImageIndex; end; - ds.Next; + Results.Next; end; finally - if ds <> nil then ds.Close; - FreeAndNil( ds ); + FreeAndNil(Results); Screen.Cursor := crDefault; end; end; @@ -245,7 +235,7 @@ end; } function TfrmSQLhelp.ShowHelpItem: Boolean; var - ds : TDataSet; + Results: TMySQLQuery; begin lblKeyword.Caption := Copy(Keyword, 0, 100); MemoDescription.Lines.Clear; @@ -253,26 +243,23 @@ begin Caption := DEFAULT_WINDOW_CAPTION; result := false; // Keyword not found yet - ds := nil; if Keyword <> '' then try Screen.Cursor := crHourglass; - ds := Mainform.GetResults( 'HELP "'+lblKeyword.Caption+'"' ); - if ds.RecordCount = 1 then - begin + Results := Mainform.Connection.GetResults('HELP "'+lblKeyword.Caption+'"'); + if Results.RecordCount = 1 then begin // We found exactly one matching help item - lblKeyword.Caption := ds.FieldByName('name').AsString; + lblKeyword.Caption := Results.Col('name'); Keyword := lblKeyword.Caption; if lblKeyword.Caption = '&' then lblKeyword.Caption := '&&'; // Avoid displaying "_" as alt-hotkey Caption := Caption + ' - ' + Keyword; - MemoDescription.Text := fixNewlines(ds.FieldByName('description').AsString); - MemoExample.Text := fixNewlines(ds.FieldByName('example').AsString); + MemoDescription.Text := fixNewlines(Results.Col('description')); + MemoExample.Text := fixNewlines(Results.Col('example')); result := true; end; finally - if ds <> nil then ds.Close; - FreeAndNil( ds ); + FreeAndNil(Results); Screen.Cursor := crDefault; end; @@ -317,7 +304,7 @@ end; } procedure TfrmSQLhelp.ButtonOnlinehelpClick(Sender: TObject); begin - ShellExec( APPDOMAIN + 'sqlhelp.php?mysqlversion='+inttostr(Mainform.mysql_version)+ + ShellExec( APPDOMAIN + 'sqlhelp.php?mysqlversion='+inttostr(Mainform.Connection.ServerVersionInt)+ '&keyword='+urlencode(keyword) ); end; diff --git a/source/synchronization.pas b/source/synchronization.pas deleted file mode 100644 index d1cea10a..00000000 --- a/source/synchronization.pas +++ /dev/null @@ -1,470 +0,0 @@ -unit synchronization; - -(* - Inter-Process Synchronization. - - Current limitations: - * Hard limit on number of windows. - * Reuses various Delphi exceptions instead of defining own. - (Both could be fixed if necessary.) -*) - - - -interface - -const - appName = 'HeidiSQL'; - (* - If you alter the structure of the shared data (TWindowData) - or the window limit (maxWindows), you must alter the string - below so the two incompatible versions of your app can coxist - peacefully. - *) - compatibilityLevel = 'v3'; - maxWindows = 99; - -type - TWindowData = packed record - appHandle: THandle; - (*connectionUid: integer;*) - name: ShortString; - namePostFix: integer; - (*ready: boolean;*) - connected: boolean; - end; - TWindowDataArray = packed array of TWindowData; - -(* - Run this procedure at application startup. - catch any exceptions and abort after showing - an error message if one occurred. -*) -procedure InitializeSync(myWindow: THandle); - -(* - Run this procedure at application shutdown. - Catch any exceptions and show an error message if one occurred. -*) -procedure DeInitializeSync; - -(* - Run this procedure when (dis-)connecting. - Set uid to a connection profile's uid, - or 0 if it's a custom connection or disconnection. - - Commented out: - We currently do not have any uniqueness handling nor versioning of connection profiles. -*) -(*procedure SetConnectionUid(uid: integer);*) - -(* - Run this procedure when starting and ending database queries. - - Commented out: - I'm not sure we want 1 connection per window. - Perhaps we want multiple tabs, each with their own connection, - in which case a per-window flag is useless. -*) -(*procedure SetWindowReady(ready: boolean);*) - -(* - Run this procedure when opening/closing connection to a host. -*) -procedure SetWindowConnected(connected: boolean); - -(* - Run this procedure when opening/closing connection. -*) -function SetWindowName(name: string): integer; - -(* - Run this procedure to get a list of application windows. - If you run this function from a timer, consider: - 1) Disable the timer at once when it's fired, reenable when event handler is all done. - 2) Before reenabling the timer, adjust it's interval value by adding 100msec * nr. of windows. -*) -function GetWindowList: TWindowDataArray; - -(* - Run this procedure to find and remove disappeared application instances' data. - If you run this function from a timer, consider: - 1) Disable the timer at once when it's fired, reenable when event handler is all done. - 2) Before reenabling the timer, adjust it's interval value by adding 100msec * nr. of windows. -*) -procedure CheckForCrashedWindows; - - - -implementation - -uses - Windows, - SysUtils; - -const - FILE_BACKING_PAGEFILE = $ffffffff; - -type - PSharedData = ^TSharedData; - TSharedData = record - windows: array[0..maxWindows] of TWindowData; - end; - -var - sharedData: PSharedData = nil; - mySlot: integer = -1; - myDataArea: THandle = 0; - myInternalLock: TRTLCriticalSection; - mutexName: PAnsiChar; - dataAreaName: PAnsiChar; - -procedure InitializeSync(myWindow: THandle); -var - mutex: THandle; - errorCode: integer; - i: integer; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of a mutually exclusive lock shared - // between multiple instances of this application. - // Create the lock if it doesn't exist. - mutex := CreateMutex(nil, true, mutexName); - if GetLastError = ERROR_ALREADY_EXISTS then begin - mutex := OpenMutex(MUTEX_ALL_ACCESS, false, mutexName); - end; - - // Die on failure to open or create lock. - if mutex = 0 then begin - raise EAssertionFailed.CreateFmt('Failed to create or open mutex ''%s'': Win32 error %d.', [mutexName, GetLastError]); - end; - - // Create or open shared data area. - myDataArea := CreateFileMapping(FILE_BACKING_PAGEFILE, nil, PAGE_READWRITE, 0, SizeOf(TSharedData), dataAreaName); - errorCode := GetLastError; - - // Die on failure to open or create shared data area. - if myDataArea = 0 then begin - raise EAssertionFailed.CreateFmt('Failed to create or open file mapping ''%s'': Win32 error %d.', [dataAreaName, errorCode]); - end; - - // Map shared data area into our process memory. - sharedData := MapViewOfFile(myDataArea, FILE_MAP_ALL_ACCESS, 0, 0, 0); - - // Die on failure to map data area. - if sharedData = nil then begin - raise EAssertionFailed.CreateFmt('Failed to map data area into memory: Win32 error %d.', [GetLastError]); - end; - - // If we've just created the data area, initialize it. - // (Not particularly needed on Windows, since it clears - // memory in a background thread before giving it to apps. - // But that's a security feature so who knows if it will - // be around as a feature later / on different platforms.) - if errorCode <> ERROR_ALREADY_EXISTS then begin - FillChar(sharedData^, SizeOf(TSharedData), 0); - end; - - // Find an empty slot and grab it. - for i := 0 to maxWindows do begin - if sharedData^.windows[i].appHandle = 0 then begin - sharedData^.windows[i].appHandle := myWindow; - (*sharedData^.windows[i].connectionUid := 0;*) - (*sharedData^.windows[i].ready := true;*) - sharedData^.windows[i].name := ''; - sharedData^.windows[i].namePostFix := 0; - sharedData^.windows[i].connected := false; - mySlot := i; - break; - end; - end; - - // Die if no empty slot was found. - if mySlot = -1 then begin - raise EAssertionFailed.CreateFmt('Failed to allocate window slot. No more than %d instances is allowed.', [maxWindows + 1]); - end; - - // Disown lock. - ReleaseMutex(mutex); - - finally - LeaveCriticalSection(myInternalLock); - end; -end; - - -// Internal helper procedure to grab the mutex and abort on error. -procedure GrabLock(var mutex: THandle); -begin - mutex := OpenMutex(MUTEX_ALL_ACCESS, false, mutexName); - - // Die on failure to open lock. - if mutex = 0 then begin - raise EAssertionFailed.CreateFmt('Failed to open mutex ''%s'': Win32 error %d.', [mutexName, GetLastError]); - end; -end; - - -function FindWindowsWithName(searchName: string; excludeSlot: integer; addPostFix: boolean; clearPostFix: boolean): integer; -var - max: integer; - i: integer; -begin - // Find out if other windows has the same name. - max := 0; - for i := 0 to maxWindows do begin - if i <> excludeSlot then with sharedData^.windows[i] do begin - if appHandle <> 0 then begin - if name = searchName then begin - if addPostFix or clearPostFix then begin - if addPostFix and (namePostFix = 0) then namePostFix := 1; - if clearPostFix then namePostFix := 0; - end else if max = 0 then max := max + 1; - if namePostFix > max then max := namePostFix; - end; - end; - end; - end; - result := max; -end; - - -procedure DeInitializeSync; -var - mutex: THandle; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of lock. - GrabLock(mutex); - - // Clear uniqueness postfix if only one window with this name is left. - if FindWindowsWithName(sharedData^.windows[mySlot].name, mySlot, false, false) = 1 then begin - FindWindowsWithName(sharedData^.windows[mySlot].name, -1, false, true); - end; - - // Remove ourselves from our slot. - if mySlot > -1 then begin - sharedData^.windows[mySlot].appHandle := 0; - mySlot := -1; - end; - - // Unmap shared data from memory. - if sharedData <> nil then begin - UnmapViewOfFile(sharedData); - sharedData := nil; - end; - if myDataArea <> 0 then begin - CloseHandle(myDataArea); - myDataArea := 0; - end; - - // We could potentially clean up the mutex if we're - // the last instance, but we really need not bother - // since Windows does it for us when we terminate. - - // Release lock. - ReleaseMutex(mutex); - - finally - LeaveCriticalSection(myInternalLock); - end; -end; - - -(* -procedure SetConnectionUid(uid: integer); -var - mutex: THandle; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of lock. - GrabLock(mutex); - - // Set UID value. - sharedData^.windows[mySlot].connectionUid := uid; - - // Release lock. - ReleaseMutex(mutex); - - finally - LeaveCriticalSection(myInternalLock); - end; -end; -*) - - -(* -procedure SetWindowReady(ready: boolean); -var - mutex: THandle; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of lock. - GrabLock(mutex); - - // Set UID value. - sharedData^.windows[mySlot].ready := ready; - - // Release lock. - ReleaseMutex(mutex); - - finally - LeaveCriticalSection(myInternalLock); - end; -end; -*) - - -procedure SetWindowConnected(connected: boolean); -var - mutex: THandle; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of lock. - GrabLock(mutex); - - // Set UID value. - sharedData^.windows[mySlot].connected := connected; - - // Release lock. - ReleaseMutex(mutex); - - finally - LeaveCriticalSection(myInternalLock); - end; -end; - - -function SetWindowName(name: string): integer; -var - mutex: THandle; - count: integer; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of lock. - GrabLock(mutex); - - // Find out if other windows has the same name, in which case set namePostFix > 0. - count := FindWindowsWithName(name, mySlot, true, false); - if count > 0 then count := count + 1; - sharedData^.windows[mySlot].namePostFix := count; - result := count; - - // Set name by copying string value into array. - sharedData^.windows[mySlot].name := name; - - // Release lock. - ReleaseMutex(mutex); - - finally - LeaveCriticalSection(myInternalLock); - end; -end; - - -function GetWindowList: TWindowDataArray; -var - mutex: THandle; - i: integer; - count: integer; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of lock. - GrabLock(mutex); - - // Find how many slots are used. - count := 0; - if sharedData <> nil then begin - for i := 0 to maxWindows do begin - if sharedData^.windows[i].appHandle <> 0 then begin - count := count + 1; - end; - end; - end; - - // Create an array with a clone of the slot data. - SetLength(result, count); - if sharedData <> nil then begin - count := 0; - for i := 0 to maxWindows do begin - if sharedData^.windows[i].appHandle <> 0 then begin - result[count] := sharedData^.windows[i]; - count := count + 1; - if count = High(result) + 1 then break; - end; - end; - end; - - // Release lock. - ReleaseMutex(mutex); - - // Hint the programmer that he might or might not have an issue. - // This exception can be safely ignored, it's ok to hit it on purpose. - if count = 0 then begin - raise ERangeError.Create('GetWindowList() called, but there is 0 instances left.'); - end; - - finally - LeaveCriticalSection(myInternalLock); - end; -end; - - -procedure CheckForCrashedWindows; -var - mutex: THandle; - count: integer; - i: integer; -begin - EnterCriticalSection(myInternalLock); - try - // Take ownership of lock. - GrabLock(mutex); - - // Remove application instance shared data if they've disappeared. - count := 0; - if sharedData <> nil then begin - for i := 0 to maxWindows do with sharedData^.windows[i] do begin - if appHandle <> 0 then begin - count := count + 1; - if not IsWindow(appHandle) then begin - // Clear uniqueness postfix if only one window with this name is left. - if FindWindowsWithName(name, i, false, false) = 0 then FindWindowsWithName(name, -1, false, true); - appHandle := 0; - end; - end; - end; - end; - - // Release lock. - ReleaseMutex(mutex); - - // Hint the programmer that he might or might not have an issue. - // This exception can be safely ignored, it's ok to hit it on purpose. - if count = 0 then begin - raise ERangeError.Create('CheckForCrashedWindows() called, but there is 0 instances left.'); - end; - - finally - LeaveCriticalSection(myInternalLock); - end; -end; - - -initialization - // The critical section is technically not absolutely necessary, - // but it makes this unit thread-safe, thereby making it easier - // to use the functions herein. - InitializeCriticalSection(myInternalLock); - mutexName := appName + '_mutex_' + compatibilityLevel; - dataAreaName := appName + '_data_' + compatibilityLevel; - -end. - diff --git a/source/table_editor.pas b/source/table_editor.pas index 06bc577f..4aac2bb2 100644 --- a/source/table_editor.pas +++ b/source/table_editor.pas @@ -5,8 +5,8 @@ interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, TntStdCtrls, ComCtrls, ToolWin, VirtualTrees, WideStrings, - SynRegExpr, ActiveX, DB, ExtCtrls, ImgList, SynEdit, SynMemo, Menus, WideStrUtils, - Contnrs, grideditlinks, mysql_structures, helpers; + SynRegExpr, ActiveX, ExtCtrls, ImgList, SynEdit, SynMemo, Menus, WideStrUtils, + Contnrs, grideditlinks, mysql_structures, mysql_connection, helpers; type TfrmTableEditor = class(TFrame) @@ -286,7 +286,7 @@ end; procedure TfrmTableEditor.Init(AlterTableName: WideString=''); var - ds: TDataset; + Results: TMySQLQuery; Props: TWideStringlist; Col: TColumn; IndexType: String; @@ -323,7 +323,7 @@ begin editMaxRows.Text := ''; chkChecksum.Checked := False; comboRowFormat.ItemIndex := 0; - comboCollation.ItemIndex := comboCollation.Items.IndexOf(Mainform.GetVar('SHOW VARIABLES LIKE ''collation_database''', 1)); + comboCollation.ItemIndex := comboCollation.Items.IndexOf(Mainform.Connection.GetVar('SHOW VARIABLES LIKE ''collation_database''', 1)); memoUnionTables.Clear; comboInsertMethod.ItemIndex := -1; @@ -333,21 +333,21 @@ begin // Editing existing table editName.Text := FAlterTableName; Mainform.SetEditorTabCaption(Self, FAlterTableName); - ds := Mainform.GetResults('SHOW TABLE STATUS LIKE '+esc(FAlterTableName)); - memoComment.Text := ds.FieldByName(DBO_COMMENT).AsWideString; - if ds.FindField(DBO_ENGINE) <> nil then - engine := ds.FieldByName(DBO_ENGINE).AsString + Results := Mainform.Connection.GetResults('SHOW TABLE STATUS LIKE '+esc(FAlterTableName)); + memoComment.Text := Results.Col(DBO_COMMENT); + if Results.ColExists(DBO_ENGINE) then + engine := Results.Col(DBO_ENGINE) else - engine := ds.FieldByName(DBO_TYPE).AsString; + engine := Results.Col(DBO_TYPE); comboEngine.ItemIndex := comboEngine.Items.IndexOf(engine); - if ds.FindField(DBO_COLLATION) <> nil then - comboCollation.ItemIndex := comboCollation.Items.IndexOf(ds.FieldByName(DBO_COLLATION).AsWideString); - editAutoInc.Text := ds.FieldByName(DBO_AUTOINC).AsString; - editAvgRowLen.Text := ds.FieldByName(DBO_AVGROWLEN).AsString; - chkChecksum.Checked := Pos('checksum=1', LowerCase(ds.FieldByName(DBO_CROPTIONS).AsString)) > 0; - comboRowFormat.ItemIndex := comboRowFormat.Items.IndexOf(ds.FieldByName(DBO_ROWFORMAT).AsString); - FreeAndNil(ds); - CreateTable := Mainform.GetVar('SHOW CREATE TABLE '+Mainform.mask(FAlterTableName), 1); + if Results.ColExists(DBO_COLLATION) then + comboCollation.ItemIndex := comboCollation.Items.IndexOf(Results.Col(DBO_COLLATION)); + editAutoInc.Text := Results.Col(DBO_AUTOINC); + editAvgRowLen.Text := Results.Col(DBO_AVGROWLEN); + chkChecksum.Checked := Pos('checksum=1', LowerCase(Results.Col(DBO_CROPTIONS))) > 0; + comboRowFormat.ItemIndex := comboRowFormat.Items.IndexOf(Results.Col(DBO_ROWFORMAT)); + FreeAndNil(Results); + CreateTable := Mainform.Connection.GetVar('SHOW CREATE TABLE '+Mainform.mask(FAlterTableName), 1); rx := TRegExpr.Create; rx.ModifierI := True; rx.Expression := '\bUNION=\((.+)\)'; @@ -496,30 +496,30 @@ begin end; FreeAndNil(rx); - ds := Mainform.GetResults('SHOW KEYS FROM '+Mainform.mask(FAlterTableName)); + Results := Mainform.Connection.GetResults('SHOW KEYS FROM '+Mainform.mask(FAlterTableName)); LastKeyName := ''; Props := nil; - while not ds.Eof do begin - if LastKeyName <> ds.FieldByName('Key_name').AsWideString then begin + while not Results.Eof do begin + if LastKeyName <> Results.Col('Key_name') then begin Props := TWideStringlist.Create; Props.OnChange := IndexesChange; - IndexType := ds.FieldByName('Key_name').AsString; - if (ds.FindField('Index_type') <> nil) and ( - (ds.FieldByName('Index_type').AsString = FKEY) or (ds.FieldByName('Index_type').AsString = SKEY)) then - IndexType := ds.FieldByName('Index_type').AsString - else if (IndexType <> PKEY) and (ds.FieldByName('Non_unique').AsInteger = 0) then + IndexType := Results.Col('Key_name'); + if Results.ColExists('Index_type') and ( + (Results.Col('Index_type') = FKEY) or (Results.Col('Index_type') = SKEY)) then + IndexType := Results.Col('Index_type') + else if (IndexType <> PKEY) and (Results.Col('Non_unique') = '0') then IndexType := UKEY - else if ds.FieldByName('Non_unique').AsInteger = 1 then + else if Results.Col('Non_unique') = '1' then IndexType := KEY; - Indexes.AddObject(ds.FieldByName('Key_name').AsWideString+REGDELIM+IndexType, Props); + Indexes.AddObject(Results.Col('Key_name')+REGDELIM+IndexType, Props); end; - Props.Add(ds.FieldByName('Column_name').AsWideString); - if ds.FieldByName('Sub_part').AsString <> '' then - Props[Props.Count-1] := Props[Props.Count-1] + '('+ds.FieldByName('Sub_part').AsString+')'; - LastKeyName := ds.FieldByName('Key_name').AsWideString; - ds.Next; + Props.Add(Results.Col('Column_name')); + if Results.Col('Sub_part') <> '' then + Props[Props.Count-1] := Props[Props.Count-1] + '('+Results.Col('Sub_part')+')'; + LastKeyName := Results.Col('Key_name'); + Results.Next; end; - FreeAndNil(ds); + FreeAndNil(Results); end; listColumns.RootNodeCount := FColumns.Count; @@ -571,9 +571,9 @@ begin Specs.Add('DROP FOREIGN KEY '+Mainform.mask(Key.KeyName)); end; if Specs.Count > 0 then - Mainform.ExecUpdateQuery('ALTER TABLE '+Mainform.mask(FAlterTableName)+' '+ImplodeStr(', ', Specs)); + Mainform.Connection.Query('ALTER TABLE '+Mainform.mask(FAlterTableName)+' '+ImplodeStr(', ', Specs)); end; - Mainform.ExecUpdateQuery(sql); + Mainform.Connection.Query(sql); // Set table name for altering if Apply was clicked FAlterTableName := editName.Text; tabALTERcode.TabVisible := FAlterTableName <> ''; @@ -645,7 +645,7 @@ var ColSpec, OldColName, IndexSQL: WideString; i: Integer; DropIt: Boolean; - ds: TDataset; + Results: TMySQLQuery; Key: TForeignKey; Col, PreviousCol: PColumn; ColObj: TColumn; @@ -678,13 +678,13 @@ begin if comboInsertMethod.Enabled and (comboInsertMethod.Tag = ModifiedFlag) and (comboInsertMethod.Text <> '') then Specs.Add('INSERT_METHOD='+comboInsertMethod.Text); if chkCharsetConvert.Checked then begin - ds := Mainform.GetCollations; - while not ds.Eof do begin - if ds.FieldByName('Collation').AsWideString = comboCollation.Text then begin - Specs.Add('CONVERT TO CHARSET '+ds.FieldByName('Charset').AsWideString); + Results := Mainform.GetCollations; + while not Results.Eof do begin + if Results.Col('Collation') = comboCollation.Text then begin + Specs.Add('CONVERT TO CHARSET '+Results.Col('Charset')); break; end; - ds.Next; + Results.Next; end; end; @@ -719,7 +719,7 @@ begin ColSpec := ColSpec + Col.Collation; end; // Server version requirement, see http://dev.mysql.com/doc/refman/4.1/en/alter-table.html - if Mainform.mysql_version >= 40001 then begin + if Mainform.Connection.ServerVersionInt >= 40001 then begin if PreviousCol = nil then ColSpec := ColSpec + ' FIRST' else @@ -2110,7 +2110,7 @@ begin MessageDlg('Please select a reference table before selecting foreign columns.', mtError, [mbOk], 0) else begin try - Mainform.GetVar('SELECT 1 FROM '+Mainform.MaskMulti(Key.ReferenceTable)); + Mainform.Connection.GetVar('SELECT 1 FROM '+Mainform.MaskMulti(Key.ReferenceTable)); Allowed := True; except // Leave Allowed = False @@ -2129,7 +2129,7 @@ var VT: TVirtualStringTree; EnumEditor: TEnumEditorLink; SetEditor: TSetEditorLink; - ds: TDataset; + Results: TMySQLQuery; Key: TForeignKey; ColNode: PVirtualNode; Col: PColumn; @@ -2151,17 +2151,17 @@ begin 2: begin EnumEditor := TEnumEditorLink.Create(VT); EnumEditor.AllowCustomText := True; - ds := Mainform.FetchActiveDbTableList; - while not ds.Eof do begin - EnumEditor.ValueList.Add(ds.FieldByName(DBO_NAME).AsWideString); - ds.Next; + Results := Mainform.FetchActiveDbTableList; + while not Results.Eof do begin + EnumEditor.ValueList.Add(Results.Col(DBO_NAME)); + Results.Next; end; EditLink := EnumEditor; end; 3: begin Key := ForeignKeys[Node.Index] as TForeignKey; SetEditor := TSetEditorLink.Create(VT); - SetEditor.ValueList := Mainform.GetCol('SHOW COLUMNS FROM '+Mainform.MaskMulti(Key.ReferenceTable)); + SetEditor.ValueList := Mainform.Connection.GetCol('SHOW COLUMNS FROM '+Mainform.MaskMulti(Key.ReferenceTable)); EditLink := SetEditor; end; 4, 5: begin diff --git a/source/tabletools.pas b/source/tabletools.pas index 6b4bf50f..32dec7e9 100644 --- a/source/tabletools.pas +++ b/source/tabletools.pas @@ -9,8 +9,8 @@ unit tabletools; interface uses - Windows, SysUtils, Classes, Controls, Forms, StdCtrls, ComCtrls, Buttons, - WideStrings, WideStrUtils, VirtualTrees, ExtCtrls, Db, Contnrs, Graphics, TntStdCtrls; + Windows, SysUtils, Classes, Controls, Forms, StdCtrls, ComCtrls, Buttons, Dialogs, + WideStrings, WideStrUtils, VirtualTrees, ExtCtrls, mysql_connection, Contnrs, Graphics, TntStdCtrls; type TfrmTableTools = class(TForm) @@ -137,7 +137,7 @@ begin TreeObjects.Clear; TreeObjects.RootNodeCount := Mainform.DBtree.RootNodeCount; // CHECKSUM available since MySQL 4.1.1 - if Mainform.mysql_version < 40101 then + if Mainform.Connection.ServerVersionInt < 40101 then comboOperation.Items[comboOperation.Items.IndexOf('Checksum')] := 'Checksum ('+STR_NOTSUPPORTED+')'; comboOperation.OnChange(Sender); end; @@ -204,7 +204,7 @@ end; procedure TfrmTableTools.TreeObjectsInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); var - ds: TDataset; + Results: TMySQLQuery; begin // Attach a checkbox to all nodes Mainform.DBtreeInitNode(Sender, ParentNode, Node, InitialStates); @@ -224,10 +224,10 @@ begin end; end; 2: begin - ds := Mainform.FetchDbTableList(Mainform.Databases[ParentNode.Index]); - ds.RecNo := Node.Index+1; + Results := Mainform.FetchDbTableList(Mainform.Databases[ParentNode.Index]); + Results.RecNo := Node.Index; // No checkbox for stored routines - if not (GetDBObjectType(ds.Fields) in [lntTable, lntCrashedTable, lntView]) then + if not (GetDBObjectType(Results) in [lntTable, lntCrashedTable, lntView]) then Node.CheckType := ctNone else begin if Node.Parent.CheckState in [csCheckedNormal, csCheckedPressed] then begin @@ -236,7 +236,7 @@ begin end else if (Mainform.Databases[Node.Parent.Index] = Mainform.ActiveDatabase) // ... or table name is in SelectedTables and (SelectedTables.Count > 0) - and (SelectedTables.IndexOf(ds.FieldByName(DBO_NAME).AsWideString) > -1) then begin + and (SelectedTables.IndexOf(Results.Col(DBO_NAME)) > -1) then begin Node.CheckState := csCheckedNormal; Node.Parent.CheckState := csMixedNormal; end; @@ -281,7 +281,7 @@ procedure TfrmTableTools.ProcessTableNode(Sender: TObject; Node: PVirtualNode); var SQL, db, table, QuotedTable: WideString; TableSize, RowsInTable: Int64; - ds: TDataset; + Results: TMySQLQuery; i: Integer; HasSelectedDatatype: Boolean; begin @@ -293,18 +293,18 @@ begin // Find table in cashed dataset and check its size - perhaps it has to be skipped TableSize := 0; RowsInTable := 0; - ds := Mainform.FetchDbTableList(db); - while not ds.Eof do begin - if (ds.FieldByName(DBO_NAME).AsWideString = table) - and (GetDBObjectType(ds.Fields) in [lntTable, lntCrashedTable]) then begin - TableSize := GetTableSize(ds); - RowsInTable := MakeInt(ds.FieldByName(DBO_ROWS).AsString); + Results := Mainform.FetchDbTableList(db); + while not Results.Eof do begin + if (Results.Col(DBO_NAME) = table) + and (GetDBObjectType(Results) in [lntTable, lntCrashedTable]) then begin + TableSize := GetTableSize(Results); + RowsInTable := MakeInt(Results.Col(DBO_ROWS)); // Avoid division by zero in below SQL if RowsInTable = 0 then RowsInTable := 1; break; end; - ds.Next; + Results.Next; end; if (udSkipLargeTables.Position = 0) or ((TableSize div SIZE_MB) < udSkipLargeTables.Position) then try if Sender = btnExecuteMaintenance then begin @@ -316,19 +316,19 @@ begin if chkChanged.Enabled and chkChanged.Checked then SQL := SQL + ' CHANGED'; if chkUseFrm.Enabled and chkUseFrm.Checked then SQL := SQL + ' USE_FRM'; end else if Sender = btnFindText then begin - ds := Mainform.GetResults('SHOW COLUMNS FROM '+QuotedTable); + Results := Mainform.Connection.GetResults('SHOW COLUMNS FROM '+QuotedTable); SQL := ''; - while not ds.Eof do begin + while not Results.Eof do begin HasSelectedDatatype := comboDatatypes.ItemIndex = 0; if not HasSelectedDatatype then for i:=Low(Datatypes) to High(Datatypes) do begin - HasSelectedDatatype := (LowerCase(getFirstWord(ds.FieldByName('Type').AsString)) = LowerCase(Datatypes[i].Name)) + HasSelectedDatatype := (LowerCase(getFirstWord(Results.Col('Type'))) = LowerCase(Datatypes[i].Name)) and (Integer(Datatypes[i].Category)+1 = comboDatatypes.ItemIndex); if HasSelectedDatatype then break; end; if HasSelectedDatatype then - SQL := SQL + Mainform.mask(ds.FieldByName('Field').AsWideString) + ' LIKE ' + esc('%'+memoFindText.Text+'%') + ' OR '; - ds.Next; + SQL := SQL + Mainform.mask(Results.Col('Field')) + ' LIKE ' + esc('%'+memoFindText.Text+'%') + ' OR '; + Results.Next; end; if SQL <> '' then begin Delete(SQL, Length(SQL)-3, 3); @@ -358,39 +358,40 @@ var i: Integer; Col: TVirtualTreeColumn; Row: TWideStringlist; - ds: TDataset; + Results: TMySQLQuery; begin // Execute query and append results into grid - ds := Mainform.GetResults(SQL); - if ds = nil then + Results := Mainform.Connection.GetResults(SQL); + if Results = nil then Exit; // Add missing columns - for i:=ResultGrid.Header.Columns.Count to ds.FieldCount-1 do begin + for i:=ResultGrid.Header.Columns.Count to Results.ColumnCount-1 do begin Col := ResultGrid.Header.Columns.Add; Col.Width := 130; end; // Remove superfluous columns - for i:=ResultGrid.Header.Columns.Count-1 downto ds.FieldCount do + for i:=ResultGrid.Header.Columns.Count-1 downto Results.ColumnCount do ResultGrid.Header.Columns[i].Free; // Set column header names - for i:=0 to ds.FieldCount-1 do begin + for i:=0 to Results.ColumnCount-1 do begin Col := ResultGrid.Header.Columns[i]; - Col.Text := ds.Fields[i].FieldName; - if ds.Fields[i].DataType in [ftSmallint, ftInteger, ftWord, ftLargeint, ftFloat] then + Col.Text := Results.ColumnNames[i]; + if Results.DataType(i).Category in [dtcInteger, dtcIntegerNamed, dtcReal] then Col.Alignment := taRightJustify else Col.Alignment := taLeftJustify; end; - ds.First; - while not ds.Eof do begin + Results.First; + while not Results.Eof do begin Row := TWideStringlist.Create; - for i:=0 to ds.FieldCount-1 do begin - Row.Add(ds.Fields[i].AsString); + for i:=0 to Results.ColumnCount-1 do begin + Row.Add(Results.Col(i)); end; FResults.Add(Row); - ds.Next; + Results.Next; end; + Results.Free; Inc(FRealResultCounter); lblResults.Caption := IntToStr(FRealResultCounter)+' results:'; diff --git a/source/usermanager.pas b/source/usermanager.pas index ae27b489..a0f3d8e3 100644 --- a/source/usermanager.pas +++ b/source/usermanager.pas @@ -5,7 +5,7 @@ interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, - ComCtrls, StdCtrls, CheckLst, ExtCtrls, Buttons, DB, + ComCtrls, StdCtrls, CheckLst, ExtCtrls, Buttons, mysql_connection, ToolWin, TntCheckLst, WideStrings, WideStrUtils, helpers; {$I const.inc} @@ -26,8 +26,8 @@ type DBONames: TWideStringList; PrivNames: TWideStringList; SelectedPrivNames: TWideStringList; - constructor Create(Fields: TFields; FieldDefs: TDataset = nil; AvoidFieldDefs: TDataSet = nil; CropFieldDefs: TDataSet = nil; SimulateDbField: Boolean = False); - procedure Merge(Fields: TFields); + constructor Create(Fields: TMySQLQuery; FieldDefs: TMySQLQuery=nil; AvoidFieldDefs: TMySQLQuery=nil; CropFieldDefs: TMySQLQuery=nil; SimulateDbField: Boolean = False); + procedure Merge(Fields: TMySQLQuery); property DBOType: TListNodeType read FDBOType; property DBOKey: String read GetDBOKey; property DBOPrettyKey: String read GetDBOPrettyKey; @@ -47,11 +47,11 @@ type function GetCount: Integer; public constructor Create(AOwner: TUser); - function AddPrivilege(Fields: TFields; FieldDefs: TDataset = nil; AvoidFieldDefs: TDataSet = nil; CropFieldDefs: TDataSet = nil; SimulateDbField: Boolean = False): TPrivilege; + function AddPrivilege(Fields: TMySQLQuery; FieldDefs: TMySQLQuery=nil; AvoidFieldDefs: TMySQLQuery=nil; CropFieldDefs: TMySQLQuery=nil; SimulateDbField: Boolean = False): TPrivilege; property Items[Index: Integer]: TPrivilege read GetPrivilege; default; property Count: Integer read GetCount; procedure DeletePrivilege(Index: Integer); - function FindPrivilege(Fields: TFields; SimulateDbField: Boolean): TPrivilege; + function FindPrivilege(Fields: TMySQLQuery; SimulateDbField: Boolean): TPrivilege; end; TUser = class(TObject) @@ -85,7 +85,7 @@ type procedure SetHost(str: String); procedure SetPassword(str: String); public - constructor Create(Fields: TFields = nil); overload; + constructor Create(Fields: TMySQLQuery=nil); overload; constructor Create(Name: String; Host: String); overload; property Name: String read GetName write SetName; property Host: String read GetHost write SetHost; @@ -202,7 +202,7 @@ type { Public declarations } end; -procedure GetPrivilegeRowKey(Fields: TFields; SimulateDbField: Boolean; out DBOType: TListNodeType; out DBONames: TWideStringList); +procedure GetPrivilegeRowKey(Fields: TMySQLQuery; SimulateDbField: Boolean; out DBOType: TListNodeType; out DBONames: TWideStringList); implementation @@ -216,7 +216,7 @@ var // Results from SELECT * FROM user/db/... dsUser, dsDb, dsTables, dsColumns, // Results from SHOW FIELDS FROM user/db/... - dsTablesFields, dsColumnsFields : TDataset; + dsTablesFields, dsColumnsFields : TMySQLQuery; {$R *.DFM} @@ -266,14 +266,14 @@ begin // Test if we can access the privileges database and tables by // A. Using the mysql-DB try - Mainform.ExecUseQuery(DBNAME_MYSQL); + Mainform.Connection.Database := DBNAME_MYSQL; except MessageDlg('You have no access to the privileges database.', mtError, [mbOK], 0); Result := false; Exit; end; // B. retrieving a count of all users. - test_result := Mainform.GetVar( 'SELECT COUNT(*) FROM '+db+'.'+Mainform.Mask(PRIVTABLE_USERS), 0, true, false ); + test_result := Mainform.Connection.GetVar('SELECT COUNT(*) FROM '+db+'.'+Mainform.Mask(PRIVTABLE_USERS)); if test_result = '' then begin MessageDlg('You have no access to the privileges tables.', mtError, [mbOK], 0); Result := false; @@ -291,26 +291,26 @@ var snr: String; begin // Set hints text - snr := Mainform.GetVar('SHOW VARIABLES LIKE ' + esc('skip_name_resolve'), 0, True, False); + snr := Mainform.Connection.GetVar('SHOW VARIABLES LIKE ' + esc('skip_name_resolve')); if snr = '' then snr := 'Unknown'; lblHostHints.Caption := StringReplace(lblHostHints.Caption, '$SNR', snr, []); // Load users into memory Users := TUsers.Create; // Enable limitations editors only if relevant columns exist - lblMaxQuestions.Enabled := dsUser.FindField('max_questions') <> nil; + lblMaxQuestions.Enabled := dsUser.ColExists('max_questions'); editMaxQuestions.Enabled := lblMaxQuestions.Enabled; udMaxQuestions.Enabled := lblMaxQuestions.Enabled; - lblMaxUpdates.Enabled := dsUser.FindField('max_updates') <> nil; + lblMaxUpdates.Enabled := dsUser.ColExists('max_updates'); editMaxQuestions.Enabled := lblMaxUpdates.Enabled; udMaxUpdates.Enabled := lblMaxUpdates.Enabled; - lblMaxConnections.Enabled := dsUser.FindField('max_connections') <> nil; + lblMaxConnections.Enabled := dsUser.ColExists('max_connections'); editMaxConnections.Enabled := lblMaxConnections.Enabled; udMaxConnections.Enabled := lblMaxConnections.Enabled; - lblMaxUserConnections.Enabled := dsUser.FindField('max_user_connections') <> nil; + lblMaxUserConnections.Enabled := dsUser.ColExists('max_user_connections'); editMaxUserConnections.Enabled := lblMaxUserConnections.Enabled; udMaxUserConnections.Enabled := lblMaxUserConnections.Enabled; @@ -600,7 +600,7 @@ end; procedure TUserManagerForm.btnAddObjectClick(Sender: TObject); var NewObj: TWideStringList; - ds, FieldDefs: TDataset; + ds, FieldDefs: TMySQLQuery; NewPriv: TPrivilege; u: TUser; i: Integer; @@ -626,7 +626,7 @@ begin else Exception.Create('Added privilege object has an invalid number of segments ('+IntToStr(NewObj.Count)+')'); end; - NewPriv := u.Privileges.AddPrivilege(ds.Fields, FieldDefs); + NewPriv := u.Privileges.AddPrivilege(ds, FieldDefs); NewPriv.Added := True; NewPriv.DBONames := NewObj; u.Modified := True; @@ -878,7 +878,7 @@ var u: TUser; p: TPrivilege; sql, TableName, SetFieldName: String; - TableSet: TDataSet; + TableSet: TMySQLQuery; AcctWhere, AcctValues, PrivWhere: String; AcctUpdates, PrivValues, PrivUpdates: TWideStringList; procedure LogSQL(sql: String); @@ -888,7 +888,7 @@ var procedure Exec(sql: String); begin //LogSQL(sql); Exit; - Mainform.ExecuteNonQuery(sql); + Mainform.Connection.Query(sql); end; function Mask(sql: String): String; begin @@ -981,16 +981,16 @@ begin end else if u.PasswordModified then AcctUpdates.Add(mask('Password') + '= PASSWORD(' + esc(u.Password) + ')'); // Apply limitation updates. - if dsUser.FindField('max_questions') <> nil then + if dsUser.ColExists('max_questions') then if u.FOldMaxQuestions <> u.MaxQuestions then AcctUpdates.Add(mask('max_questions') + '=' + IntToStr(u.MaxQuestions)); - if dsUser.FindField('max_updates') <> nil then + if dsUser.ColExists('max_updates') then if u.FOldMaxUpdates <> u.MaxUpdates then AcctUpdates.Add(mask('max_updates') + '=' + IntToStr(u.MaxUpdates)); - if dsUser.FindField('max_connections') <> nil then + if dsUser.ColExists('max_connections') then if u.FOldMaxConnections <> u.MaxConnections then AcctUpdates.Add(mask('max_connections') + '=' + IntToStr(u.MaxConnections)); - if dsUser.FindField('max_user_connections') <> nil then + if dsUser.ColExists('max_user_connections') then if u.FOldMaxUserConnections <> u.MaxUserConnections then AcctUpdates.Add(mask('max_user_connections') + '=' + IntToStr(u.MaxUserConnections)); // Skip accounts with fx only username / host changes, they've already been processed. @@ -1020,20 +1020,20 @@ begin else AcctUpdates.Add(mask('Password') + '=PASSWORD(' + esc(u.Password) + ')'); // Apply limits. - if dsUser.FindField('max_questions') <> nil then + if dsUser.ColExists('max_questions') then AcctUpdates.Add(mask('max_questions') + '=' + IntToStr(u.MaxQuestions)); - if dsUser.FindField('max_updates') <> nil then + if dsUser.ColExists('max_updates') then AcctUpdates.Add(mask('max_updates') + '=' + IntToStr(u.MaxUpdates)); - if dsUser.FindField('max_connections') <> nil then + if dsUser.ColExists('max_connections') then AcctUpdates.Add(mask('max_connections') + '=' + IntToStr(u.MaxConnections)); - if dsUser.FindField('max_user_connections') <> nil then + if dsUser.ColExists('max_user_connections') then AcctUpdates.Add(mask('max_user_connections') + '=' + IntToStr(u.MaxUserConnections)); // Special case: work around missing default values (bug) in MySQL. - if dsUser.FindField('ssl_cipher') <> nil then + if dsUser.ColExists('ssl_cipher') then AcctUpdates.Add(mask('ssl_cipher') + '=' + esc('')); - if dsUser.FindField('x509_issuer') <> nil then + if dsUser.ColExists('x509_issuer') then AcctUpdates.Add(mask('x509_issuer') + '=' + esc('')); - if dsUser.FindField('x509_subject') <> nil then + if dsUser.ColExists('x509_subject') then AcctUpdates.Add(mask('x509_subject') + '=' + esc('')); sql := 'INSERT INTO ' + db + '.' + mask(PRIVTABLE_USERS); sql := sql + ' SET ' + Delim(AcctUpdates); @@ -1095,7 +1095,7 @@ begin if (p.DBOType = lntDb) and (p.DBOKey = '%') then begin PrivUpdates.Clear; for k := 0 to p.PrivNames.Count - 1 do begin - if dsUser.FindField(p.PrivNames[k] + '_priv') <> nil then + if dsUser.ColExists(p.PrivNames[k] + '_priv') then PrivUpdates.Add(mask(p.PrivNames[k] + '_priv') + '=' + esc('N')); end; sql := 'UPDATE ' + db + '.' + mask(PRIVTABLE_USERS); @@ -1113,7 +1113,7 @@ begin // instead, we have to set them manually to 'N'. PrivUpdates.Clear; for k := 0 to p.PrivNames.Count - 1 do - if TableSet.FindField(p.PrivNames[k] + '_priv') <> nil then + if TableSet.ColExists(p.PrivNames[k] + '_priv') then PrivUpdates.Add(mask(p.PrivNames[k] + '_priv') + '=' + esc('N')); sql := 'UPDATE ' + db + '.' + TableName; sql := sql + ' SET ' + Delim(PrivUpdates); @@ -1137,7 +1137,7 @@ begin // Assemble values of new privilege definition. for k := 0 to p.PrivNames.Count - 1 do begin if p.SelectedPrivNames.IndexOf(p.PrivNames[k]) > -1 then c := 'Y' else c := 'N'; - if TableSet.FindField(p.PrivNames[k] + '_priv') <> nil then begin + if TableSet.ColExists(p.PrivNames[k] + '_priv') then begin // There's an ENUM field matching the privilege name. PrivUpdates.Add(mask(p.PrivNames[k] + '_priv') + '=' + esc(c)); end else @@ -1153,11 +1153,11 @@ begin if (p.DBOType = lntNone) then begin // Server barfs if we do not set missing defaults, sigh. PrivValues.Clear; - if dsUser.FindField('ssl_cipher') <> nil then + if dsUser.ColExists('ssl_cipher') then PrivValues.Add(mask('ssl_cipher') + '=' + esc('')); - if dsUser.FindField('x509_issuer') <> nil then + if dsUser.ColExists('x509_issuer') then PrivValues.Add(mask('x509_issuer') + '=' + esc('')); - if dsUser.FindField('x509_subject') <> nil then + if dsUser.ColExists('x509_subject') then PrivValues.Add(mask('x509_subject') + '=' + esc('')); if PrivValues.Count > 0 then sql := sql + ', ' + Delim(PrivValues); @@ -1166,7 +1166,7 @@ begin end; Exec(sql); // Special case: update redundant column privileges in mysql.tables_priv. - if (p.DBOType = lntColumn) and (dsTables.FindField('column_priv') <> nil) then begin + if (p.DBOType = lntColumn) and dsTables.ColExists('column_priv') then begin // We need to deduce a completely new key because column_priv in mysql.tables_priv does not have a column field next to it, sigh. PrivUpdates.Clear; PrivUpdates.Add(mask('Host') + '=' + esc(u.Host)); @@ -1200,55 +1200,55 @@ var i: Integer; user, host: WideString; begin - dsUser := Mainform.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_USERS) + ' ORDER BY ' + dsUser := Mainform.Connection.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_USERS) + ' ORDER BY ' + Mainform.Mask('User')+', ' + Mainform.Mask('Host')); - dsDb := Mainform.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_DB) + dsDb := Mainform.Connection.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_DB) // Ignore db entries that contain magic pointers to the mysql.host table. + ' WHERE Db <> '#39#39 + ' ORDER BY ' + Mainform.Mask('User')+', ' + Mainform.Mask('Host')+', ' + Mainform.Mask('Db')); - dsTables := Mainform.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_TABLES) + ' ORDER BY ' + dsTables := Mainform.Connection.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_TABLES) + ' ORDER BY ' + Mainform.Mask('User')+', ' + Mainform.Mask('Host')+', ' + Mainform.Mask('Db')+', ' + Mainform.Mask('Table_name')); - dsColumns := Mainform.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_COLUMNS) + ' ORDER BY ' + dsColumns := Mainform.Connection.GetResults('SELECT * FROM '+db+'.'+Mainform.Mask(PRIVTABLE_COLUMNS) + ' ORDER BY ' + Mainform.Mask('User')+', ' + Mainform.Mask('Host')+', ' + Mainform.Mask('Db')+', ' + Mainform.Mask('Table_name')+', ' + Mainform.Mask('Column_name')); - dsTablesFields := Mainform.GetResults('SHOW FIELDS FROM '+db+'.tables_priv LIKE ''%\_priv'''); - dsColumnsFields := Mainform.GetResults('SHOW FIELDS FROM '+db+'.columns_priv LIKE ''%\_priv'''); - for i := 1 to dsUser.RecordCount do begin + dsTablesFields := Mainform.Connection.GetResults('SHOW FIELDS FROM '+db+'.tables_priv LIKE ''%\_priv'''); + dsColumnsFields := Mainform.Connection.GetResults('SHOW FIELDS FROM '+db+'.columns_priv LIKE ''%\_priv'''); + for i:=0 to dsUser.RecordCount-1 do begin // Avoid using dsUser.Next and dsUser.Eof here because TUser.Create // also iterates through the global dsUser result and moves the cursor dsUser.RecNo := i; - u := TUser.Create(dsUser.Fields); + u := TUser.Create(dsUser); AddUser(u); end; // Find orphaned privileges in mysql.db. - for i := 1 to dsDb.RecordCount do begin + for i:=0 to dsDb.RecordCount-1 do begin dsDb.RecNo := i; - user := dsDb.Fields.FieldByName('User').AsWideString; - host := dsDb.Fields.FieldByName('Host').AsWideString; + user := dsDb.Col('User'); + host := dsDb.Col('Host'); if FindUser(user, host) = nil then AddUser(TUser.Create(user, host)); end; // Find orphaned privileges in mysql.tables_priv. - for i := 1 to dsTables.RecordCount do begin + for i:=0 to dsTables.RecordCount-1 do begin dsTables.RecNo := i; - user := dsTables.Fields.FieldByName('User').AsWideString; - host := dsTables.Fields.FieldByName('Host').AsWideString; + user := dsTables.Col('User'); + host := dsTables.Col('Host'); if FindUser(user, host) = nil then AddUser(TUser.Create(user, host)); end; // Find orphaned privileges in mysql.columns_priv. - for i := 1 to dsColumns.RecordCount do begin + for i:=0 to dsColumns.RecordCount-1 do begin dsColumns.RecNo := i; - user := dsColumns.Fields.FieldByName('User').AsWideString; - host := dsColumns.Fields.FieldByName('Host').AsWideString; + user := dsColumns.Col('User'); + host := dsColumns.Col('Host'); if FindUser(user, host) = nil then AddUser(TUser.Create(user, host)); end; end; @@ -1322,7 +1322,7 @@ begin FPrivileges := TPrivileges.Create(Self); end; -constructor TUser.Create(Fields: TFields); +constructor TUser.Create(Fields: TMySQLQuery); begin // Loading an existing user FAdded := False; @@ -1331,8 +1331,8 @@ begin FPasswordModified := False; FDisabled := False; FNameChanged := False; - FOldName := Fields.FieldByName('User').AsWideString; - FOldHost := Fields.FieldByName('Host').AsWideString; + FOldName := Fields.Col('User', True); + FOldHost := Fields.Col('Host', True); if FOldHost = '' then begin // Get rid of duplicate entries. // Todo: handle collisions. @@ -1340,22 +1340,22 @@ begin FOtherModified := True; end; FPassword := ''; - FOldPasswordHashed := Fields.FieldByName('Password').AsString; + FOldPasswordHashed := Fields.Col('Password', True); FOldMaxQuestions := 0; - if Fields.FindField('max_questions') <> nil then - FOldMaxQuestions := MakeInt(Fields.FieldByName('max_questions').AsString); + if Fields.ColExists('max_questions') then + FOldMaxQuestions := MakeInt(Fields.Col('max_questions')); FMaxQuestions := FOldMaxQuestions; FOldMaxUpdates := 0; - if Fields.FindField('max_updates') <> nil then - FOldMaxUpdates := MakeInt(Fields.FieldByName('max_updates').AsString); + if Fields.ColExists('max_updates') then + FOldMaxUpdates := MakeInt(Fields.Col('max_updates')); FMaxUpdates := FOldMaxUpdates; FOldMaxConnections := 0; - if Fields.FindField('max_connections') <> nil then - FOldMaxConnections := MakeInt(Fields.FieldByName('max_connections').AsString); + if Fields.ColExists('max_connections') then + FOldMaxConnections := MakeInt(Fields.Col('max_connections')); FMaxConnections := FOldMaxConnections; FOldMaxUserConnections := 0; - if Fields.FindField('max_user_connections') <> nil then - FOldMaxUserConnections := MakeInt(Fields.FieldByName('max_user_connections').AsString); + if Fields.ColExists('max_user_connections') then + FOldMaxUserConnections := MakeInt(Fields.Col('max_user_connections')); FMaxUserConnections := FOldMaxUserConnections; FPrivileges := TPrivileges.Create(Self); @@ -1413,30 +1413,30 @@ var p: TPrivilege; host, user: String; // Extract privilege objects from results of user, db, tables_priv + columns_priv - procedure LoadPrivs(ds: TDataset; FieldDefs: TDataset = nil; AvoidFieldDefs: TDataset = nil; CropDbFieldDefs: TDataset = nil); + procedure LoadPrivs(ds: TMySQLQuery; FieldDefs: TMySQLQuery=nil; AvoidFieldDefs: TMySQLQuery=nil; CropDbFieldDefs: TMySQLQuery=nil); var hasUserField : Boolean; simulateDb : Boolean; begin ds.First; // The priv table 'host' does not have a user field, use '%'. - hasUserField := ds.FieldDefs.IndexOf('User') > -1; + hasUserField := ds.ColExists('User'); user := '%'; // When cropping the priv table 'user' to load only db privileges, use '%' for db. simulateDb := cropDbFieldDefs <> nil; while not ds.Eof do begin - if hasUserField then user := ds.FieldByName('User').AsWideString; - host := ds.FieldByName('Host').AsWideString; + if hasUserField then user := ds.Col('User'); + host := ds.Col('Host'); // Canonicalize: Host='' and Host='%' means the same. if host = '' then host := '%'; if (host = FOwner.FOldHost) and (user = FOwner.FOldName) then begin // Find existing privilege, or create + add new one. - p := FindPrivilege(ds.Fields, simulateDb); + p := FindPrivilege(ds, simulateDb); if (p = nil) then begin - p := AddPrivilege(ds.Fields, FieldDefs, AvoidFieldDefs, CropDbFieldDefs, simulateDb); + p := AddPrivilege(ds, FieldDefs, AvoidFieldDefs, CropDbFieldDefs, simulateDb); end; // Merge grants from row into the privilege object. - p.Merge(ds.Fields) + p.Merge(ds) end; ds.Next; end; @@ -1445,8 +1445,8 @@ begin FOwner := AOwner; if AOwner.Added then begin // Create blanket "server privileges" and "all objects" items. - AddPrivilege(dsUser.Fields, nil, dsDb); - AddPrivilege(dsUser.Fields, nil, nil, dsDb, True); + AddPrivilege(dsUser, nil, dsDb); + AddPrivilege(dsUser, nil, nil, dsDb, True); end; // Load server privileges from 'user' rows, avoiding db privileges. LoadPrivs(dsUser, nil, dsDb); @@ -1468,7 +1468,7 @@ begin Result := Length(FPrivilegeItems); end; -function TPrivileges.FindPrivilege(Fields: TFields; SimulateDbField: Boolean): TPrivilege; +function TPrivileges.FindPrivilege(Fields: TMySQLQuery; SimulateDbField: Boolean): TPrivilege; var i : Integer; DBOType: TListNodeType; @@ -1487,7 +1487,7 @@ begin end; end; -function TPrivileges.AddPrivilege(Fields: TFields; FieldDefs: TDataset = nil; AvoidFieldDefs: TDataSet = nil; CropFieldDefs: TDataSet = nil; SimulateDbField: Boolean = False): TPrivilege; +function TPrivileges.AddPrivilege(Fields: TMySQLQuery; FieldDefs: TMySQLQuery=nil; AvoidFieldDefs: TMySQLQuery=nil; CropFieldDefs: TMySQLQuery=nil; SimulateDbField: Boolean = False): TPrivilege; begin Result := TPrivilege.Create(Fields, FieldDefs, AvoidFieldDefs, CropFieldDefs, SimulateDbField); SetLength(FPrivilegeItems, Length(FPrivilegeItems)+1); @@ -1518,20 +1518,19 @@ end; { *** TPrivilege *** } -constructor TPrivilege.Create(Fields: TFields; FieldDefs: TDataset = nil; AvoidFieldDefs: TDataSet = nil; CropFieldDefs: TDataSet = nil; SimulateDbField: Boolean = False); +constructor TPrivilege.Create(Fields: TMySQLQuery; FieldDefs: TMySQLQuery=nil; AvoidFieldDefs: TMySQLQuery=nil; CropFieldDefs: TMySQLQuery=nil; SimulateDbField: Boolean = False); var i: Integer; tables_col_ignore: Boolean; - cropNames: TWideStringList; // Find possible values for the given SET column function GetSETValues(FieldName: String): TWideStringList; begin FieldDefs.First; Result := TWideStringList.Create; while not FieldDefs.Eof do begin - if FieldDefs.FieldByName('Field').AsWideString = FieldName + '_priv' then begin + if FieldDefs.Col('Field') = FieldName + '_priv' then begin Result.QuoteChar := ''''; - Result.DelimitedText := getEnumValues( FieldDefs.FieldByName('Type').AsWideString ); + Result.DelimitedText := getEnumValues(FieldDefs.Col('Type')); end; FieldDefs.Next; end; @@ -1545,22 +1544,20 @@ begin PrivNames := TWideStringList.Create; SelectedPrivNames := TWideStringList.Create; // Find out what xxxx_priv privilege columns this server/version has. - Fields.GetFieldNames(PrivNames); + PrivNames.Text := Fields.ColumnNames.Text; for i := PrivNames.Count - 1 downto 0 do begin if Length(PrivNames[i]) > 5 then begin if Copy(PrivNames[i], Length(PrivNames[i]) - 4, 5) = '_priv' then begin // Avoid duplicated db privileges in user table. if AvoidFieldDefs = nil then Continue; - if AvoidFieldDefs.FieldDefs.IndexOf(PrivNames[i]) = -1 then Continue; + if AvoidFieldDefs.ColumnNames.IndexOf(PrivNames[i]) = -1 then Continue; end; end; - PrivNames.Delete(i) + PrivNames.Delete(i); end; if CropFieldDefs <> nil then begin - cropNames := TWideStringList.Create; - CropFieldDefs.Fields.GetFieldNames(cropNames); for i := PrivNames.Count - 1 downto 0 do begin - if cropNames.IndexOf(PrivNames[i]) = -1 then PrivNames.Delete(i); + if CropFieldDefs.ColumnNames.IndexOf(PrivNames[i]) = -1 then PrivNames.Delete(i); end; end; // Find out what SET columns in tables_priv this server/version has. @@ -1590,34 +1587,29 @@ begin GetPrivilegeRowKey(Fields, SimulateDbField, FDBOType, DBONames); end; -procedure TPrivilege.Merge(Fields: TFields); +procedure TPrivilege.Merge(Fields: TMySQLQuery); var i: Integer; tmp: TStringList; - fieldNames: TStringList; begin - fieldNames := TStringList.Create; - Fields.GetFieldNames(fieldNames); // Apply ENUM privileges, skipping any SET privileges. for i := PrivNames.Count - 1 downto 0 do begin - if fieldNames.IndexOf(PrivNames[i] + '_priv') > -1 then begin - if UpperCase(Fields.FieldByName(PrivNames[i] + '_priv').AsString) = 'Y' then begin + if Fields.ColumnNames.IndexOf(PrivNames[i] + '_priv') > -1 then begin + if UpperCase(Fields.Col(PrivNames[i] + '_priv')) = 'Y' then begin SelectedPrivNames.Add(PrivNames[i]); end; end; end; // Parse SET field in column "tables_priv" - i := fieldNames.IndexOf('Table_priv'); - if i > -1 then begin + if Fields.ColExists('Table_priv') then begin tmp := TStringList.Create; - tmp.CommaText := Fields.FieldByName('Table_priv').AsString; + tmp.CommaText := Fields.Col('Table_priv'); SelectedPrivNames.AddStrings(tmp); end else begin // Parse SET field in column "columns_priv" - i := fieldNames.IndexOf('Column_priv'); - if i > -1 then begin + if Fields.ColExists('Column_priv') then begin tmp := TStringList.Create; - tmp.CommaText := Fields.FieldByName('Column_priv').AsString; + tmp.CommaText := Fields.Col('Column_priv'); SelectedPrivNames.AddStrings(tmp); end; end; @@ -1663,7 +1655,7 @@ begin end; -procedure GetPrivilegeRowKey(Fields: TFields; SimulateDbField: Boolean; out DBOType: TListNodeType; out DBONames: TWideStringList); +procedure GetPrivilegeRowKey(Fields: TMySQLQuery; SimulateDbField: Boolean; out DBOType: TListNodeType; out DBONames: TWideStringList); begin DBOType := lntNone; DBONames := TWideStringList.Create; @@ -1672,17 +1664,17 @@ begin DBOType := lntDb; DBONames.Add('%'); end; - if Fields.FindField('Db') <> nil then begin + if Fields.ColExists('Db') then begin DBOType := lntDb; - DBONames.Add(Fields.FieldByName('Db').AsString); + DBONames.Add(Fields.Col('Db')); end; - if Fields.FindField('Table_name') <> nil then begin + if Fields.ColExists('Table_name') then begin DBOType := lntTable; - DBONames.Add(Fields.FieldByName('Table_name').AsString); + DBONames.Add(Fields.Col('Table_name')); end; - if Fields.FindField('Column_name') <> nil then begin + if Fields.ColExists('Column_name') then begin DBOType := lntColumn; - DBONames.Add(Fields.FieldByName('Column_name').AsString); + DBONames.Add(Fields.Col('Column_name')); end; end; diff --git a/source/view.pas b/source/view.pas index 6ac194f8..7cddbe72 100644 --- a/source/view.pas +++ b/source/view.pas @@ -4,7 +4,7 @@ interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, - Dialogs, StdCtrls, ComCtrls, SynEdit, SynMemo, ExtCtrls, DB, SynRegExpr; + Dialogs, StdCtrls, ComCtrls, SynEdit, SynMemo, ExtCtrls, mysql_connection, SynRegExpr; type TfrmView = class(TFrame) @@ -55,8 +55,8 @@ end; } procedure TfrmView.Init(EditViewName: WideString=''); var - ds: TDataset; - db: String; + Results: TMySQLQuery; + db: WideString; rx: TRegExpr; begin FEditViewName := EditViewName; @@ -65,21 +65,21 @@ begin editName.Text := FEditViewName; Mainform.SetEditorTabCaption(Self, FEditViewName); db := Mainform.ActiveDatabase; - ds := Mainform.GetResults('SELECT * FROM '+Mainform.mask(DBNAME_INFORMATION_SCHEMA)+'.VIEWS ' + + Results := Mainform.Connection.GetResults('SELECT * FROM '+Mainform.mask(DBNAME_INFORMATION_SCHEMA)+'.VIEWS ' + 'WHERE TABLE_SCHEMA = '+esc(db)+' AND TABLE_NAME = '+esc(FEditViewName)); - if ds.RecordCount = 0 then + if Results.RecordCount = 0 then raise Exception.Create('Can''t find view definition for "'+FEditViewName+'" in '+DBNAME_INFORMATION_SCHEMA); // Algorithm is not changeable as we cannot look up its current state! rgAlgorithm.Enabled := False; rgAlgorithm.ItemIndex := 0; - rgCheck.ItemIndex := rgCheck.Items.IndexOf(ds.FieldByName('CHECK_OPTION').AsString); - rgCheck.Enabled := ds.FieldByName('IS_UPDATABLE').AsString = 'YES'; + rgCheck.ItemIndex := rgCheck.Items.IndexOf(Results.Col('CHECK_OPTION')); + rgCheck.Enabled := Results.Col('IS_UPDATABLE') = 'YES'; rx := TRegExpr.Create; rx.ModifierG := True; rx.ModifierI := True; rx.Expression := '\s+WITH\s+\w+\s+CHECK\s+OPTION$'; - SynMemoSelect.Text := rx.Replace(ds.FieldByName('VIEW_DEFINITION').AsString, ''); + SynMemoSelect.Text := rx.Replace(Results.Col('VIEW_DEFINITION'), ''); rx.Free; end else begin // Create mode @@ -160,11 +160,11 @@ begin sql := sql + 'WITH '+Uppercase(rgCheck.Items[rgCheck.ItemIndex])+' CHECK OPTION'; // Execute query and keep form open in any error case - Mainform.ExecUpdateQuery(sql); + Mainform.Connection.Query(sql); // Probably rename view if (FEditViewName <> '') and (FEditViewName <> editName.Text) then begin renamed := Mainform.mask(editName.Text); - Mainform.ExecUpdateQuery('RENAME TABLE '+viewname + ' TO '+renamed); + Mainform.Connection.Query('RENAME TABLE '+viewname + ' TO '+renamed); end; Mainform.RefreshTreeDB(Mainform.ActiveDatabase); end;