|
![]() |
|
Registriert seit: 6. Mär 2007 Ort: Osnabrück 620 Beiträge Delphi XE6 Enterprise |
#1
Jede Datenbank hat virtuelle Tabellen mit Metadaten, aus denen man entnehmen kann, wie die Datenbank aufgebaut ist, wie die Tabellen heißen, welche Felder sie haben, welche Typen die haben usw. Die musst Du einfach abfragen, so bekommst Du die benötigten Infos.
Siehe ![]() |
![]() |
Registriert seit: 11. Okt 2003 Ort: Elbflorenz 44.340 Beiträge Delphi 12 Athens |
#2
Ach ja, bezüglich der AppVersion und DBVersion,
denn es kann ja auch vorkommen, dass * man mit unterschiedlichen ProgrammVersionen auf die selbe DB zugreift * oder mit einem Programm auf unterschiedliche Datenbanken. Für bestimmte Dinge, im Programm, benötigt es eine gewisse DB-Version, aber für gewisse Funktionen/Strukturen/Daten, benötigt es eine gewisse Programm-Version. (Minimal-Version und Maximal-Version) Die Anwendung setzt also voraus, dass die DB eine bestimmte (die gleiche) Version besitzt, bzw. in einem vorgegebenen Versionsbereich vorliegt. Und/oder andersrum, eine bestimmte DB-Version setzt eine Anwendung vor, welche in einem gewissenen Versionsbereich liegt.
Ein Therapeut entspricht 1024 Gigapeut.
Geändert von himitsu (11. Feb 2025 um 10:32 Uhr) |
![]() |
Registriert seit: 24. Mai 2017 Ort: Wien, Österreich 1.241 Beiträge Delphi 12 Athens |
#3
Nachtrag: Wenn man TMS Aurelius als ORM verwendet, dann zieht das ORM die DB automatisch mit.
Certified Delphi Developer (2025)
|
![]() |
Registriert seit: 22. Feb 2017 1.002 Beiträge |
#4
Danke für die zahlreichen Inspirationen/Informationen.
Jede Datenbank hat virtuelle Tabellen mit Metadaten, aus denen man entnehmen kann, wie die Datenbank aufgebaut ist, wie die Tabellen heißen, welche Felder sie haben, welche Typen die haben usw. Die musst Du einfach abfragen, so bekommst Du die benötigten Infos.
Siehe ![]() - Neue Tabellen zur erzeugen - Weggefallene Tabellen zu löschen - Neue Columns hinzufügen - Weggefallene Columns zu entfernen - Datetype und Länge der Columns überprüfen und anpassen - Defaultwert der Columns überprüfen und anpassen Das konnte ich jetzt mit eine kleine Klasse erledigen, die ich hier mal reinsetze falls das mal irgendwer gebrauchen kann.
Delphi-Quellcode:
Aufruf:
unit uTestTables2;
interface uses System.Classes, System.SysUtils, ZConnection, ZDataset, Data.DB, System.Generics.Collections, uRWStream; type TModifyFlag=(mfNoChanges, mfAdd, mfModify, mfDelete); TColumnModifyFlag=(cmfNone, cmfDatatype, cmfDefaultValue); TDBColumn=class private FName: string; FColumnDefault: string; FColumnType: string; FModifyFlag: TModifyFlag; FColumnModifyFlag: TColumnModifyFlag; procedure SetColumnDefault(const Value: string); procedure SetColumnType(const Value: string); procedure SetName(const Value: string); procedure SetModifyFlag(const Value: TModifyFlag); procedure SetColumnModifyFlag(const Value: TColumnModifyFlag); public constructor Create; procedure WriteToStream(st: TStream); procedure ReadFromStream(st: TStream; Version: Integer); procedure Compare(Source: TObject); procedure Assign(Source: TObject); function GetDefault: string; published property Name: string read FName write SetName; property ColumnType: string read FColumnType write SetColumnType; property ColumnDefault: string read FColumnDefault write SetColumnDefault; property ModifyFlag: TModifyFlag read FModifyFlag write SetModifyFlag; property ColumnModifyFlag: TColumnModifyFlag read FColumnModifyFlag write SetColumnModifyFlag; end; TDBTable=class(TObjectList<TDBColumn>) private FName: string; FCollation: string; FModifyFlag: TModifyFlag; procedure SetCollation(const Value: string); procedure SetName(const Value: string); procedure SetModifyFlag(const Value: TModifyFlag); public procedure WriteToStream(st: TStream); procedure ReadFromStream(st: TStream; Version: Integer); procedure Compare(Source: TObject); function GetColumnByName(value: string): TDBColumn; procedure Assign(Source: TObject); published property Name: string read FName write SetName; property Collation: string read FCollation write SetCollation; property ModifyFlag: TModifyFlag read FModifyFlag write SetModifyFlag; end; TDBDatabase=class(TObjectList<TDBTable>) private FFilename: string; FSchemaName: string; FCollation: string; procedure SetCollation(const Value: string); procedure SetSchemaName(const Value: string); function GetSchema(Con: TZConnection; Schema: string): Boolean; procedure GetTables(Con: TZConnection; Schema: string); procedure GetColumns(Con: TZConnection; Schema: string; TT: TDBTable); public procedure Compare(Source: TObject); procedure BuildFromDB(Con: TZConnection; RootPass: string; Schema: string); procedure LoadFromFile(Filename: string = ''); procedure SaveToFile(Filename: string = ''); function GetTableByName(value: string): TDBTable; function IsModified: Boolean; function CreateTableScript(Script: TStrings): Boolean; function ExecSQLLine(Con: TZConnection; Line: string): Boolean; published property SchemaName: string read FSchemaName write SetSchemaName; property Collation: string read FCollation write SetCollation; end; Const FileVersion = 1; StringSeparator = '`'; implementation { TDBColumn } procedure TDBColumn.Assign(Source: TObject); var TC: TDBColumn; begin if Source is TDBColumn then begin TC:=TDBColumn(Source); self.FName:=TC.Name; self.FColumnType:=TC.ColumnType; self.FColumnDefault:=TC.ColumnDefault; end; end; procedure TDBColumn.Compare(Source: TObject); var TC: TDBColumn; begin Self.FModifyFlag:=mfNoChanges; if Source is TDBColumn then begin TC:=TDBColumn(Source); if (self.FColumnDefault<>TC.ColumnDefault) then begin Self.FModifyFlag:=mfModify; self.FColumnModifyFlag:=cmfDefaultValue; self.FColumnDefault:=TC.ColumnDefault; end; if (self.FColumnType<>TC.ColumnType) then begin Self.FModifyFlag:=mfModify; self.FColumnModifyFlag:=cmfDatatype; self.FColumnType:=TC.ColumnType; end; end; end; constructor TDBColumn.Create; begin inherited; Self.FColumnModifyFlag:=cmfNone; end; function TDBColumn.GetDefault: string; begin if self.FColumnDefault<>'NULL' then Result:=''''+self.FColumnDefault+'''' else Result:=Self.FColumnDefault; end; procedure TDBColumn.ReadFromStream(st: TStream; Version: Integer); begin if st<>nil then begin self.FName:=ReadStrFromStream(st); self.FColumnType:=ReadStrFromStream(st); self.FColumnDefault:=ReadStrFromStream(st); end; end; procedure TDBColumn.SetColumnDefault(const Value: string); begin FColumnDefault := Value; end; procedure TDBColumn.SetColumnModifyFlag(const Value: TColumnModifyFlag); begin FColumnModifyFlag := Value; end; procedure TDBColumn.SetColumnType(const Value: string); begin FColumnType := Value; end; procedure TDBColumn.SetModifyFlag(const Value: TModifyFlag); begin FModifyFlag := Value; end; procedure TDBColumn.SetName(const Value: string); begin FName := Value; end; procedure TDBColumn.WriteToStream(st: TStream); begin if st<>nil then begin WriteStrToStream(st, self.FName); WriteStrToStream(st, self.FColumnType); WriteStrToStream(st, self.FColumnDefault); end; end; { TDBTable } procedure TDBTable.Assign(Source: TObject); var TT: TDBTable; TC: TDBColumn; I: Integer; begin if Source is TDBTable then begin TT:=TDBTable(Source); self.FName:=TT.Name; self.FCollation:=TT.Collation; self.Clear; for I := 0 to TT.Count-1 do begin TC:=TDBColumn.Create; TC.Assign(TT[i]); TC.ModifyFlag:=mfAdd; self.Add(TC); end; end; end; procedure TDBTable.Compare(Source: TObject); var TT: TDBTable; TC, TC2: TDBColumn; I: Integer; begin if Source is TDBTable then begin TT:=TDBTable(Source); for I := 0 to Self.Count-1 do begin TC:=TT.GetColumnByName(self[i].Name); if TC<>nil then self[i].Compare(TC) else self[i].ModifyFlag:=mfDelete; if self[i].ModifyFlag<>mfNoChanges then self.FModifyFlag:=mfModify; end; for I := 0 to TT.Count-1 do begin TC:=self.GetColumnByName(TT[i].Name); if TC=nil then begin TC2:=TDBColumn.Create; TC2.Assign(TC); TC2.ModifyFlag:=mfAdd; self.Add(TC2); self.ModifyFlag:=mfModify; end; end; end; end; function TDBTable.GetColumnByName(value: string): TDBColumn; var I: Integer; begin Result:=nil; for I := 0 to Self.Count-1 do if self[i].Name.ToLower=value.ToLower then begin Result:=self[i]; Break; end; end; procedure TDBTable.ReadFromStream(st: TStream; Version: Integer); var TC: TDBColumn; I, C: Integer; begin if st<>nil then begin Self.Clear; self.FName:=ReadStrFromStream(st); self.FCollation:=ReadStrFromStream(st); C:=ReadIntFromStream(st); for I := 0 to C-1 do begin TC:=TDBColumn.Create; TC.ReadFromStream(st, Version); self.Add(TC); end; end; end; procedure TDBTable.SetCollation(const Value: string); begin FCollation := Value; end; procedure TDBTable.SetModifyFlag(const Value: TModifyFlag); begin FModifyFlag := Value; end; procedure TDBTable.SetName(const Value: string); begin FName := Value; end; procedure TDBTable.WriteToStream(st: TStream); var I: Integer; begin if st<>nil then begin WriteStrToStream(st, self.FName); WriteStrToStream(st, self.FCollation); WriteIntToStream(st, self.Count); for I := 0 to self.Count-1 do self[i].WriteToStream(st); end; end; { TDBDatabase } procedure TDBDatabase.BuildFromDB(Con: TZConnection; RootPass: string; Schema: string); var FCon: TZConnection; FQ: TZQuery; begin FCon:=TZConnection.Create(nil); try FCon.HostName:=Con.HostName; FCon.Port:=Con.Port; FCon.User:='root'; Fcon.Password:=RootPass; Fcon.Database:='information_schema'; Fcon.Protocol:=Con.Protocol; Fcon.LibraryLocation:=Con.LibraryLocation; Fcon.LibLocation:=Con.LibLocation; try FCon.Connect; if FCon.Connected then begin self.Clear; self.FSchemaName:=''; self.FCollation:=''; if self.GetSchema(FCon, Schema) then begin Self.GetTables(FCon, Schema); end; end; except end; finally FCon.Free; end; end; procedure TDBDatabase.Compare(Source: TObject); var DB: TDBDatabase; TT, TT2: TDBTable; I: Integer; begin if Source is TDBDatabase then begin DB:=TDBDatabase(Source); for I := 0 to self.Count-1 do begin TT:=DB.GetTableByName(self[i].Name); if TT<>nil then self[i].Compare(TT) else self[i].ModifyFlag:=mfDelete; end; for i:=0 to db.Count-1 do begin TT:=self.GetTableByName(DB[i].Name); if TT=nil then begin TT2:=TDBTable.Create(True); TT2.Assign(DB[i]); TT2.ModifyFlag:=mfAdd; self.Add(TT2); end; end; end; end; function TDBDatabase.CreateTableScript(Script: TStrings): Boolean; var s: string; I: Integer; C: Integer; begin Script.Clear; Result:=False; try Script.Add('USE '+StringSeparator+self.SchemaName+StringSeparator+';'); for I := 0 to self.Count-1 do begin case self[i].ModifyFlag of mfAdd: begin s:='CREATE TABLE '+StringSeparator+self[i].Name+StringSeparator+' ('; for C := 0 to self[i].Count-1 do begin s:=s+StringSeparator+self[i][C].Name+StringSeparator+' '+self[i][c].ColumnType+' DEFAULT '+self[i][c].ColumnDefault+' ,'; end; s:=Copy(s, 1, Length(s)-2); s:=s+') COLLATE='''+self[i].Collation+''';'; Script.Add(S); end; mfModify: begin for C := 0 to self[i].Count-1 do case self[i][c].ModifyFlag of mfModify: begin s:='ALTER TABLE '+StringSeparator+self[i].Name+StringSeparator+' CHANGE COLUMN '+StringSeparator+self[i][C].Name+StringSeparator+' '+StringSeparator+self[i][C].Name+StringSeparator+' '+self[i][c].ColumnType+' DEFAULT '+self[i][c].GetDefault+';'; Script.Add(s); end; mfAdd: begin s:='ALTER TABLE '+StringSeparator+self[i].Name+StringSeparator+' ADD COLUMN '+StringSeparator+self[i][C].Name+StringSeparator+' '+self[i][c].ColumnType+' DEFAULT '+self[i][c].GetDefault+';'; Script.Add(s); end; mfDelete: begin s:='ALTER TABLE '+StringSeparator+self[i].Name+StringSeparator+' DROP COLUMN '+StringSeparator+self[i][C].Name+StringSeparator+';'; Script.Add(s); end; end; end; mfDelete: begin s:='DROP TABLE '+StringSeparator+self[i].Name+StringSeparator+';'; Script.Add(s); end; end; end; Result:=True; finally end; end; function TDBDatabase.ExecSQLLine(Con: TZConnection; Line: string): Boolean; var q: TZQuery; begin Result:=False; q:=TZQuery.Create(nil); try q.Connection:=Con; q.SQL.Text:=Line; q.ExecSQL; Result:=True; finally q.Free; end; end; procedure TDBDatabase.GetColumns(Con: TZConnection; Schema: string; TT: TDBTable); var q: TZQuery; TC: TDBColumn; begin q:=TZQuery.Create(nil); try q.Connection:=Con; q.SQL.Text:='Select * from COLUMNS where TABLE_SCHEMA=:schema and TABLE_NAME=:tablename order by ORDINAL_POSITION'; q.Params.ParamValues['schema']:=Schema; q.Params.ParamValues['tablename']:=TT.Name; q.Active:=True; while not q.Eof do begin TC:=TDBColumn.Create; TT.Add(TC); TC.Name:=q.FieldByName('COLUMN_NAME').AsString; TC.ColumnType:=q.FieldByName('COLUMN_TYPE').AsString; TC.FColumnDefault:=q.FieldByName('COLUMN_DEFAULT').AsString; q.Next; end; q.Active:=False; finally q.Free; end; end; function TDBDatabase.GetSchema(Con: TZConnection; Schema: string): Boolean; var q: TZQuery; begin Result:=False; q:=TZQuery.Create(nil); try q.Connection:=Con; q.SQL.Text:='Select * from SCHEMATA where SCHEMA_NAME=:schema'; q.Params.ParamValues['schema']:=Schema; q.Active:=True; if q.RecordCount>0 then begin self.FSchemaName:=q.FieldByName('SCHEMA_NAME').AsString; self.FCollation:=q.FieldByName('DEFAULT_COLLATION_NAME').AsString; Result:=True; end; q.Active:=False; finally q.Free; end; end; function TDBDatabase.GetTableByName(value: string): TDBTable; var I: Integer; begin Result:=nil; for I := 0 to Self.Count-1 do if self[i].Name.ToLower=value.ToLower then begin Result:=self[i]; Break; end; end; procedure TDBDatabase.GetTables(Con: TZConnection; Schema: string); var q: TZQuery; TT: TDBTable; TC: TDBColumn; begin q:=TZQuery.Create(nil); try q.Connection:=Con; q.SQL.Text:='Select * from TABLES where TABLE_SCHEMA=:schema order by TABLE_NAME'; q.Params.ParamValues['schema']:=Schema; q.Active:=True; while not q.Eof do begin TT:=TDBTable.Create(True); self.Add(TT); TT.Name:=q.FieldByName('TABLE_NAME').AsString; TT.Collation:=q.FieldByName('TABLE_COLLATION').AsString; GetColumns(Con, Schema, TT); q.Next; end; q.Active:=False; finally q.Free; end; end; function TDBDatabase.IsModified: Boolean; var I: Integer; begin Result:=False; for I := 0 to self.Count-1 do if self[i].ModifyFlag<>mfNoChanges then begin Result:=True; Break; end; end; procedure TDBDatabase.LoadFromFile(Filename: string); var st: TFileStream; Version: Integer; I, C: Integer; TT: TDBTable; begin if Filename<>'' then FFilename:=Filename; if FileExists(FFilename) then begin Self.Clear; self.FSchemaName:=''; self.FCollation:=''; st:=TFileStream.Create(FFilename, fmOpenRead); if st<>nil then try Version:=ReadIntFromStream(st); self.FSchemaName:=ReadStrFromStream(st); self.FCollation:=ReadStrFromStream(st); C:=ReadIntFromStream(st); for I := 0 to C-1 do begin TT:=TDBTable.Create(True); TT.ReadFromStream(st, Version); self.Add(TT); end; finally st.Free; end; end; end; procedure TDBDatabase.SaveToFile(Filename: string); var st: TFileStream; I: Integer; begin if Filename<>'' then FFilename:=Filename; if DirectoryExists(ExtractFilePath(FFilename)) then begin st:=TFileStream.Create(FFilename, fmCreate); if st<>nil then try WriteIntToStream(st, FileVersion); WriteStrToStream(st, self.FSchemaName); WriteStrToStream(st, self.FCollation); WriteIntToStream(st, self.Count); for I := 0 to self.Count-1 do self[i].WriteToStream(st); finally st.Free; end; end; end; procedure TDBDatabase.SetCollation(const Value: string); begin FCollation := Value; end; procedure TDBDatabase.SetSchemaName(const Value: string); begin FSchemaName := Value; end; end.
Delphi-Quellcode:
Die Proceduren LoadFromFile, SaveToFile, ReadFromStream und WriteToStream muss man zum Testen rausschmeißen.
var
DB, DB2: TDBDatabase; sl: TStringList; I: Integer; q: TZQuery; begin DB:=TDBDatabase.Create(True); DB2:=TDBDatabase.Create(True); sl:=TStringList.Create; try if ExtactDBResource then begin DB.BuildFromDB(con1, '123456', 'testschema'); //Hier muss das Root-Passwort der DB verwendet werden. DB2.LoadFromFile(ExtractFilePath(Application.ExeName)+'testschema.shdat'); DB.Compare(DB2); if DB.IsModified then begin if DB.CreateTableScript(sl) then begin if sl.Count>1 then begin for I := 1 to sl.Count-1 do begin DB.ExecSQLLine(con1, sl[i]); end; end; end; end; end; finally DB.Free; DB2.Free; sl.Free; end; Wer die dafür notwendige Unit haben möchte, kann mich ja anschreiben. Und wer nicht ZEOS verwendet, muss das für seine vewendete DB-Komponenten anpassen. Das ganze ist natürlich recht einfach gehalten, weil es mir ja nur um die o.g. Tabellen- und Feldeigenschaften geht. Wer mehr will, kann das ja entsprechend anpassen.
Gruß Hobbycoder
Alle sagten: "Das geht nicht.". Dann kam einer, der wusste das nicht, und hat's einfach gemacht. |
![]() |
Registriert seit: 24. Mai 2017 Ort: Wien, Österreich 1.241 Beiträge Delphi 12 Athens |
#5
Wow. Das ist ja eine Kanone für Spatzen. Ich weiß nicht. Änderungen an der Db ziehen oft Änderungen an den Daten nach sich. Manchmal werden Felder umbenannt + der Inhalt darf aber nicht verloren gehen. Ebenso wenn sich der Typ eienes Feldes ändert. Manche Änderungen können nur gemcht werden, wenn Constraints/Indeces/Trigger uä deaktiviert und danach aktiviert werden. Das zu Automatisieren scheint mir schwierig zu sein.
Wie gesagt, ich bin mit sowas völlig zufrieden, weil ich ja selber weiß, was sich ändern muss.
Code:
Result := Result and DoVersion(nVersion, 16, 'ALTER TABLE TBLMATERIAL DROP COLUMN BIO; ALTER TABLE TBLMATERIAL ADD BIO T_CHAR NULL',
'Spalte Bio in der Tabelle Material erstellen ...'); Result := Result and DoVersion(nVersion, 17, 'ALTER TABLE TBLMATERIAL ALTER COLUMN QUIDTEXT T_LONGTEXT', 'Die Grösse der Spalte Quidtext in der Tabelle Material wird erhöht ...'); Result := Result and DoVersion(nVersion, 18, 'ALTER TABLE TBLMATERIAL ADD LOSGROESSE T_INTEGER DEFAULT 1', 'Bei den Materialien kann eine Losgröße hinterlegt werden ...'); Result := Result and DoVersion(nVersion, 19, 'UPDATE TBLMATERIAL SET LOSGROESSE= 1', 'Losgröße auf 1 setzen ...'); Result := Result and DoVersion(nVersion, 20, 'ALTER TABLE TBLMATERIAL ADD BIOZUTATEN T_TEXT;', 'Feld für Biozutaten ergänzen ...'); Result := Result and DoVersion(nVersion, 21, 'ALTER TABLE TBLSPEISEPLAN ADD MENU T_CODE;', 'Feld für Menü ergänzen ...'); Result := Result and DoVersion(nVersion, 22, 'ALTER TABLE TBLMATERIAL ADD ZL_TAGS T_TEXT;' + 'ALTER TABLE TBLZUTAT ADD ZL_PRO_BT T_BOOLEAN;', 'Materialstamm TAGS ergänzen ...'); s := GetSQLResult('SELECT name FROM sysobjects with (nolock) WHERE (name LIKE ''%LOS%'')'); if (Trim(s) <> '') then begin Result := Result and DoVersion(nVersion, 23, 'ALTER TABLE TBLMATERIAL DROP CONSTRAINT ' + s, 'Andert die Spalte Lossgröße in der Materialien ...'); Result := Result and DoVersion(nVersion, 23, 'ALTER TABLE TBLMATERIAL ALTER COLUMN LOSGROESSE T_FLOAT', 'Andert die Spalte Lossgröße in der Materialien ...'); end;
Certified Delphi Developer (2025)
|
![]() |
Registriert seit: 22. Feb 2017 1.002 Beiträge |
#6
Wow. Das ist ja eine Kanone für Spatzen. Ich weiß nicht. Änderungen an der Db ziehen oft Änderungen an den Daten nach sich. Manchmal werden Felder umbenannt + der Inhalt darf aber nicht verloren gehen. Ebenso wenn sich der Typ eienes Feldes ändert. Manche Änderungen können nur gemcht werden, wenn Constraints/Indeces/Trigger uä deaktiviert und danach aktiviert werden. Das zu Automatisieren scheint mir schwierig zu sein.
Eine Erweiterung um Indices etc. könnte man da ja noch nach dem gleichen Prizip machen. Meine Anforderungen an die Datenbank sind beim mir (und dem Projekt) sicherlich um einiges kleiner, als bei deinen Projekten. In meinen Tabellen ändern sich in der Regel keine Column- oder Tabellen-Namen. Mir geht es mehr darum, dass durch Erweiterung des Projekt Felder und Tabellen hinzukommen und das sicher und ohne großen Aufwand vom Benutzer umgesetzt werden kann.
Gruß Hobbycoder
Alle sagten: "Das geht nicht.". Dann kam einer, der wusste das nicht, und hat's einfach gemacht. Geändert von Hobbycoder (11. Feb 2025 um 19:24 Uhr) |
![]() |
Ansicht |
![]() |
![]() |
![]() |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
![]() |
![]() |