////////////////////////////////////////////////// // SQL Server Data Access Components // Copyright © 1998-2007 Core Lab. All right reserved. ////////////////////////////////////////////////// {$IFNDEF CLR} {$I Sdac.inc} unit MSDump; {$ENDIF} interface uses SysUtils, Classes, DB, CRAccess, DBAccess, MSAccess, DADump, DAScript; type TMSDump = class; //TMSDumpObject = (doData); //TMSDumpObjects = set of TMSDumpObject; TMSDumpOptions = class(TDADumpOptions) protected FIdentityInsert: boolean; procedure AssignTo(Dest: TPersistent); override; public constructor Create(Owner: TMSDump); published property IdentityInsert: boolean read FIdentityInsert write FIdentityInsert default False; end; TMSDump = class(TDADump) protected //FObjects: TMSDumpObjects; procedure AssignTo(Dest: TPersistent); override; function GetTableInfoClass: TTableInfoClass; override; function GetConnection: TMSConnection; procedure SetConnection(Value: TMSConnection); function GetTableNames: string; override; procedure SetTableNames(Value: string); override; function CreateOptions: TDADumpOptions; override; function CreateScript: TDAScript; override; function GetOptions: TMSDumpOptions; procedure SetOptions(Value: TMSDumpOptions); procedure InternalBackup(Query: string); override; public constructor Create(Owner: TComponent); override; published property Connection: TMSConnection read GetConnection write SetConnection; //property Objects: TMSDumpObjects read FObjects write FObjects default [doData]; property Options: TMSDumpOptions read GetOptions write SetOptions; end; implementation uses {$IFDEF CLR} System.Text, System.Runtime.InteropServices, {$ELSE} CLRClasses, {$ENDIF} {$IFDEF VER6P} Variants, {$ENDIF} MemUtils, MemData, DAConsts, DALoader, OLEDBAccess, MSScript; { TMSDumpOptions } constructor TMSDumpOptions.Create(Owner: TMSDump); begin inherited Create(Owner); FIdentityInsert := False; end; procedure TMSDumpOptions.AssignTo(Dest: TPersistent); begin inherited; if Dest is TMSDumpOptions then begin TMSDumpOptions(Dest).IdentityInsert := IdentityInsert; end; end; { TMSDump } constructor TMSDump.Create(Owner: TComponent); begin inherited Create(Owner); //FObjects := [doData]; FLeftQuote := OLEDBAccess.LeftQuote; FRightQuote := OLEDBAccess.RightQuote; end; procedure TMSDump.AssignTo(Dest: TPersistent); begin inherited; if Dest is TMSDump then begin //TMSDump(Dest).Objects := Objects; end; end; function TMSDump.GetTableInfoClass: TTableInfoClass; begin Result := TOLEDBTableInfo; end; function TMSDump.GetConnection: TMSConnection; begin Result := TMSConnection(inherited Connection); end; procedure TMSDump.SetConnection(Value: TMSConnection); begin inherited Connection := Value; end; function TMSDump.GetTableNames: string; begin Result := TableNamesFromList(FTables); end; procedure TMSDump.SetTableNames(Value: string); begin TableNamesToList(Value, FTables); end; function TMSDump.CreateOptions: TDADumpOptions; begin Result := TMSDumpOptions.Create(nil); end; function TMSDump.CreateScript: TDAScript; begin Result := TMSScript.Create(nil); end; function TMSDump.GetOptions: TMSDumpOptions; begin Result := TMSDumpOptions(inherited Options); end; procedure TMSDump.SetOptions(Value: TMSDumpOptions); begin inherited Options := Value; end; {$IFNDEF CLR} type _StringBuilder = class (StringBuilder); {$ENDIF} procedure TMSDump.InternalBackup(Query: string); var MSQuery: TMSQuery; TableCount: integer; procedure VarToMSSQL(Field: TField; FieldDesc: TOLEDBFieldDesc; sb: StringBuilder); var dt: TDateTime; Blob: TBlob; Piece: PPieceHeader; Value: Variant; Data: TBytes; {$IFNDEF VER6P} pValueData: PVarData; {$ENDIF} {$IFDEF CLR} Bytes: TBytes; {$ELSE} sbOffset: integer; {$ENDIF} begin SetLength(Data, 0); // To avoid Hint from compiler case FieldDesc.DataType of dtBoolean: sb.Append(BoolToStr(Field.AsBoolean)); dtUnknown, dtString, dtMemo, dtWideMemo, dtExtString, dtWideString, dtExtWideString, dtGuid{$IFDEF VER5P}, dtVariant{$ENDIF}: sb.Append(QuotedStr(Field.AsString)); dtInt8, dtInt16, dtInt32, dtInt64, dtUInt16, dtUInt32: begin {$IFNDEF VER6P} Value := Field.AsVariant; pValueData := @TVarData(Value); if pValueData.VType = varDecimal then sb.Append(IntToStr(PInt64(@pValueData.VInteger)^)) else {$ENDIF} sb.Append(Field.AsString); end; dtFloat, dtCurrency: sb.Append(ChangeDecimalSeparator(Field.AsString)); dtDate, dtTime, dtDateTime: begin dt := Field.AsDateTime; if dt = 0 then sb.Append(QuotedStr('1900-01-01 00:00:00')) else sb.Append(QuotedStr(FormatDateTime('YYYY-MM-DD HH:NN:SS', dt))); end; dtBlob: begin sb.Append('0x'); Blob := MSQuery.GetBlob(Field.FieldName); Piece := Blob.FirstPiece; {$IFDEF CLR} while IntPtr(Piece) <> nil do begin SetLength(Data, Piece.Used); Marshal.Copy(IntPtr(Integer(Piece) + sizeof(TPieceHeader)), Data, 0, Piece.Used); SetLength(Bytes, Length(Data) * 2); BinToHex(Data, 0, Bytes, 0, Length(Data)); sb.Append(Encoding.Default.GetString(Bytes)); Piece := Piece.Next; end; {$ELSE} sbOffset := sb.Length + 1; sb.Length := sb.Length + Integer(Blob.Size) * 2; while Piece <> nil do begin BinToHex(IntPtr(Integer(Piece) + sizeof(TPieceHeader)), IntPtr(Integer(@_StringBuilder(sb).FString[sbOffset])), Piece.Used); sbOffset := sbOffset + Integer(Piece.Used); Piece := Piece.Next; end; {$ENDIF} end; dtBytes, dtVarBytes, dtExtVarBytes: begin sb.Append('0x'); Value := Field.Value; Data := Value; {$IFDEF CLR} SetLength(Bytes, Length(Data) * 2); BinToHex(Data, 0, Bytes, 0, Length(Data)); sb.Append(Encoding.Default.GetString(Bytes)); {$ELSE} sbOffset := sb.Length + 1; sb.Length := sb.Length + Length(Data) * 2; BinToHex(@Data[0], IntPtr(Integer(@_StringBuilder(sb).FString[sbOffset])), Length(Data)); {$ENDIF} end; else Assert(False, 'Unknown datatype (' + IntToStr(FieldDesc.DataType) + ')'); end; end; procedure BackupTablesAndData; function FieldIsIdentity(Field: TField): boolean; var FieldDesc: TOLEDBFieldDesc; begin FieldDesc := MSQuery.GetFieldDesc(Field) as TOLEDBFieldDesc; Result := FieldDesc.IsAutoIncrement; end; procedure BackupTable(TableName: string; TableNum: integer); var KeyAndDataFields: TKeyAndDataFields; procedure GetCurrentRow(sb: StringBuilder); var sbOldLen: integer; procedure ProcessField(Field: TField); var Value: Variant; FieldDesc: TOLEDBFieldDesc; begin if sbOldLen <> sb.Length then sb.Append(', '); Value := Field.AsVariant; if VarIsEmpty(Value) or VarIsNull(Value) or Field.IsNull then sb.Append('NULL') else begin FieldDesc := MSQuery.GetFieldDesc(Field) as TOLEDBFieldDesc; VarToMSSQL(Field, FieldDesc, sb); end; end; var i: integer; begin sbOldLen := sb.Length; if Length(KeyAndDataFields.DataFieldDescs) = 0 then begin for i := 0 to MSQuery.FieldCount - 1 do if not (FieldIsIdentity(MSQuery.Fields[i]) and not Options.IdentityInsert) then ProcessField(MSQuery.Fields[i]); end else begin if (TDBAccessUtils.GetIdentityField(MSQuery) <> nil) and Options.IdentityInsert then ProcessField(TDBAccessUtils.GetIdentityField(MSQuery)); for i := 0 to Length(KeyAndDataFields.DataFieldDescs) - 1 do ProcessField(MSQuery.GetField(KeyAndDataFields.DataFieldDescs[i])); end; end; var RecordCount: integer; InsHeader: string; FieldList: string; i: integer; SQLSelect1: string; sb: StringBuilder; begin FieldList := ''; if Query = '' then SQLSelect1 := 'SELECT * FROM ' + TableName else SQLSelect1 := Query; MSQuery.SQL.Text := SQLSelect1; MSQuery.AddWhere('0=1'); try MSQuery.Open; if (TableName = '') and (TDBAccessUtils.GetTablesInfo(MSQuery).Count > 0) then TableName := TDBAccessUtils.GetTablesInfo(MSQuery)[0].TableName; if (TDBAccessUtils.GetIdentityField(MSQuery) <> nil) and Options.IdentityInsert then FieldList := QuoteName(TDBAccessUtils.GetIdentityField(MSQuery).FieldName); TDBAccessUtils.GetKeyAndDataFields(MSQuery, KeyAndDataFields, False); for i := 0 to Length(KeyAndDataFields.DataFieldDescs) - 1 do if not (TOLEDBFieldDesc(KeyAndDataFields.DataFieldDescs[i]).IsAutoIncrement and not Options.IdentityInsert) then if FieldList = '' then FieldList := QuoteName(KeyAndDataFields.DataFieldDescs[i].Name) else FieldList := FieldList + ', ' + QuoteName(KeyAndDataFields.DataFieldDescs[i].Name); finally MSQuery.Close; end; if True {(doData in Objects)} then begin if Options.GenerateHeader then AddLineToSQL(SBHTableData, [TableName]); if Options.AddDrop {and not (doTables in Objects)} then Add('TRUNCATE TABLE ' + TableName + ';'); if Options.IdentityInsert then Add(Format('SET IDENTITY_INSERT %s ON;', [TableName])); MSQuery.SQL.Text := 'SELECT COUNT(*) FROM ' + TableName; MSQuery.Execute; RecordCount := MSQuery.Fields[0].AsInteger; if Assigned(FOnBackupProgress) then FOnBackupProgress(Self, TableName, TableNum, TableCount, 0); if RecordCount > 0 then begin if FieldList = '' then InsHeader := TableName else InsHeader := TableName + '(' + FieldList + ')'; InsHeader := 'INSERT INTO ' + InsHeader + ' VALUES'; MSQuery.SQL.Text := SQLSelect1; MSQuery.Open; if FieldList <> '' then TDBAccessUtils.GetKeyAndDataFields(MSQuery, KeyAndDataFields, False); sb := StringBuilder.Create; try while not MSQuery.Eof do begin if Assigned(FOnBackupProgress) then FOnBackupProgress(Self, TableName, TableNum, TableCount, Trunc(MSQuery.RecNo * 100 / RecordCount)); sb.Length := 0; sb.Append(InsHeader); sb.Append(' ('); GetCurrentRow(sb); sb.Append(');'); Add(sb.ToString); MSQuery.Next; end; finally sb.Free; end; end; if Options.IdentityInsert then Add(Format('SET IDENTITY_INSERT %s OFF;', [TableName])); end; end; var i: integer; TablesList: TStringList; TableName: string; begin if Query = '' then begin TablesList := nil; try TablesList := TStringList.Create; if FTables.Count = 0 then GetTablesList(Connection, TablesList) else TablesList.Assign(FTables); for i := 0 to TablesList.Count - 1 do begin TableName := QuoteName(TablesList[i]); if Assigned(FOnBackupProgress) then FOnBackupProgress(Self, TableName, i, TablesList.Count, 0); TableCount := TablesList.Count; BackupTable(TableName, i); Add(''); end; finally TablesList.Free; end; end else begin if FTables.Count = 1 then TableName := QuoteName(FTables[0]) else TableName := ''; if Assigned(FOnBackupProgress) then FOnBackupProgress(Self, TableName, 0, 1, 0); TableCount := 1; BackupTable(TableName, 0); Add(''); end; end; begin BeginConnection; try Query := Trim(Query); if (Query <> '') and (FTables.Count > 1) then raise Exception.Create(SWrongTblCount); MSQuery := nil; FSQL.BeginUpdate; try FSQL.Text := ''; if Options.GenerateHeader then AddLineToSQL(Format(SBHCaption, ['Sdac', SDACVersion, 'MS SQL', Connection.ServerVersion, 'MS SQL', Connection.ClientVersion, DateTimeToStr(Now), Connection.Server, Connection.Database])); MSQuery := TMSQuery.Create(nil); MSQuery.Connection := Connection; MSQuery.ReadOnly := False; MSQuery.FetchAll := True; MSQuery.UniDirectional := True; MSQuery.Options.SetFieldsReadOnly := True; MSQuery.Options.QueryRecCount := False; // if doData in Objects then BackupTablesAndData; finally FSQL.EndUpdate; MSQuery.Free; end; finally EndConnection; end; end; end.