Kleine dynamische Datenbank im Interface-Stil

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Kleine dynamische Datenbank im Interface-Stil

Beitrag von NicTheQuick »

Hallo Community!

Ich arbeite gerade an einer kleinen dynamischen Tabellendatenbank, die
ich für den zukünftigen PBOR benutzen möchte. Bisher fehlen noch
jegliche Optimierungen für die Suche in der Datenbank, aber ich habe
schon einige Ideen.

Hier ist der Code mit kommentiertem Beispiel am Ende. Leider habe ich
gerade keine Zeit um eine Anleitung zu schreiben, aber ihr werdet euch
sicherlich auch so zurechtfinden. Wenn jemand Fehler findet, bitte direkt
melden. So wird auch automatisch der zukünftige PBOR von diesen
Fehlern verschont bleiben.

Aber jetzt endlich mal der Code.

///Edit 1:
Nach helpys Vorschlag nun eine neue Benennung mit 'Row' für Zeilen
und 'Col' (Column) für Spalten.
Außerdem habe ich gerade in der Elektrotechnik-AG die Zeit ein bisschen
genutzt und eine neue DumpToFile-Funktion geschrieben und getestet.
Diese Funktion setzt allerdings eine schon geöffnete Datei voraus,
wodurch man dann auch mehrere Tabellen in eine Datei schreiben kann.
Die entsprechende Lesefunktion fehlt allerdings noch. Daran werde ich
mich morgen setzen.
Danach fehlt nur noch die Komprimierungsroutine mit dem
BriefLZ-Algorithmus, den ich damals schon für den PBOR benutzt hatte und
den Rings mir schon vorzeitig bereit gestellt hat. Danke nochmal an ihn.

///Edit 2:
Eine neue Version der Datenbank ist online!
+ Added: Passive DumpToFile-Funktion, d.h. man muss die Datei selbst öffnen. Rückgabe sind die geschriebenen Bytes
+ Added: Mit der Funktion DB_Free kann die Datenbank aus dem Speicher gelöscht werden
+ Added: DB_CreateFromDump erstellt eine Datenbank aus einer Datei, die zuvor geöffnet werden muss
+ Fixed: Fehler, wenn man mehr als 128 Einträge erstellen will
+ Fixed: Noch andere blöde Fehler

///Edit 3:
+ Changed: Speicher- und Ladefunktion für die Liste, aber ohne Rückgabe der geschriebenen Bytes
+ Changed: "DB_"-Präfix entfernt
+ Added: Clear()-Funktion zum Löschen der Datenbank, aber nicht auflösen des Interfaces
+ Added: Memory-Typ 'm' - um ganze Speicherblöcke in die Datenbank zu werfen. Ausgelesen wird immer nur der Pointer zum Speicher. Löschfunktion folgt noch, bis dahin einfach einen leeren String übergeben.

///Edit 4:
+ Fixed: Kleiner Fehler, den ich schonmal hatte
+ Changed: bessere interne Verwaltung der Rows (Zeilen)

///Edit 5:
+ Changed: Alle SelectRowByEntry()-Funktionen beginnen die Suche auf der aktuellen Zeile. Also vorher immer FirstRow() benutzen, wenn es nicht anders gewünscht ist
+ Changed: SelectRowByEntryS() funktioniert jetzt auch mit Wildcards, wenn der optionale Parameter mask auf #True gesetzt wird.
+ Added: SelectRowByEntryD() wählt auf Wunsch alle Doubles aus, die in einem bestimmten Bereich liegen, den man mit dem Parameter range angeben kann. Praktisch wegen den Ungenauigkeiten bei Fließkommazahlen
+ Added: Alle DB_ListByEntry()-Funktionen funktionieren wie die SelectRowByEntry()-Funktionen, erwarten aber eine LinkedList des Typs l, in die alle Indices geschrieben werden, wo der Suchparameter zutrifft. Leider kann ich die Funktionen nicht ins Interface mitaufnehmen, weil dort keine LinkedLists angenommen werden. Bug?

///Edit 6:
+ Added: MatchCase-Parameter nach Umsetzung von Kiffi zum Finden von Strings unabhängig oder abhängig von Groß- und Kleinschreibung.

///Edit 7:
+ Fixed: Kleiner Fehler nach dem Benutzen von ReadDB() und anfügen weiterer Zeilen
+ Added: SetChunkRowSize() nach Kiffis Vorschlag.

///Edit 8: Es gibt einige Änderungen
+ Added: ResetRow() oder SelectRow(0) setzt die aktuelle Zeile vor die erste (ähnlich LinkedList)
+ Changed: Alle SelectRowByEntry()-Funktionen suchen ab einen Eintrag nach dem aktuellen (siehe Beispiele)
+ Added: DeleteRow() hat jetzt einen opt. Parameter zur Auswahl der Zeile
+ Added: GetChunkRowSize() hinzugefügt
+ Added: AddRow() hat einen neuen opt. Parameter für die Angabe der Zeile, hinter der eine neue hinzugefügt werden soll. Mit 0 wird eine Zeile vor der ersten hinzugefügt.

Nun sind die Codes online: ("Ziel speichern unter..." benutzen)

DB.pbi (Die Datenbank als Include)
CompareWithWildcards.pbi (Die Include für die Datenbank)
Beispiele.pb (Beispiele für die Datenbank) <- Nicht mehr vorhanden

Ich habe die ersten beiden Includes ins jaPBe-Include-Verzeichnis gelegt.
Sehr zu empfehlen! :)

///Edit 9:
Vorweg schonmal: Die Funktion DelCol() bitte noch nicht benutzen. Sie funktioniert noch nicht wirklich richtig.

+Added: AddCol() hat einen neuen zusätzlichen Parameter, mit dem man eine Spalte auch zwischendrin einfügen kann und es geht genauso schnell wie das Einfügen hintendran.
+Added: AddRow() bietet jetzt per optionalen Parameter die Möglichkeit auf einen Schlag einen ganzen Block neuer Zeilen an beliebiger Position zu erstellen.
+Added: LoadDB() zum Laden einer Datenbank mit Angabe des Dateinamens
+Added: SaveDB() zum Speichern eine Datenbank mit Angabe des Dateinamens
+Added: LoadCSV() zum Laden einer CSV-Datei, wie man sie mit Excel exportieren kann. Standardmäßig wird der erste Datensatz der CSV-Datei als Spaltennamen verwendet. Per Parameter kann das Verhalten geändert werden.
+Added: SaveCSV() zum Speicher als CSV-Datei
+Added: Show() öffnet ein Fenster, das die Datenbank zeigt. Hier können auch einzelne Einträge gelöscht werden. Eigentlich nur praktisch zu Debug-Zwecken

+Fixed: Ein paar kleinere Fehler wurden behoben, die anscheinend immer nur mir auffallen. :)

///Edit 10:

+Added: GetLastError() : gibt die letzte Fehlermeldung als Nummer zurück
+Added: SetNoError() : setzt die letzte Fehlermeldung auf "NoError"
+Added: GetErrorText(Error.l) : gibt die letzte oder eine bestimmte Fehlermeldung als Text zurück
+Added: SwapCols() : Tauscht zwei Spalten
+Added: SwapRows() : Tauscht zwei Zeilen
+Fixed: DelCol() : funktioniert jetzt anstandslos, aber AddCol() ist noch nicht angepasst. Es gibt trotzdem keine Fehler, nur in bestimmten Situationen schlechte Speichernutzung. Zu besseren Speichernutzung also immer Complete auf #True setzen.

///Edit 11:
So, es gibt wieder ein Update.

+Added: PokeDB() schreibt die Datenbank in einen Speicherbereich
+Added: PeekDB() liest die Datenbank aus einem Speicherbereich
+Added: Size() gibt die Größe der DB, die zum Speichern benötigt wird, an
+Fixed: WriteDB() (somit auch SaveDB()) schreibt jetzt ein etwas anderes Format, womit die Dateigröße pro Zeile auch noch um 4 Byte schrumpft
+Changed: ReadDB() (somit auch LoadDB()) auf WriteDB() angepasst
+Added: Eine Fehlermeldung kam durch PeekDB() und PokeDB() hinzu
+Fixed: Ein paar interne Änderungen, die zu Fehler hätten führen können

Ansonsten bin ich wirklich schwer am Grübeln wie ich das jetzt mit dem
Spalten löschen machen soll, damit der Platz, der beim Löschen entsteht, beim Anfügen einer neuen Spalte auch anständig genutzt wird.

Naja, wie gesagt: Ich bin dran!

///Edit 12:
Und hier kommt das neue Update, komplett überarbeitet... *schwitz*

+Added: SelectEntryByPtr() wählt eine Zeile per Pointer aus (Pointer ändert sich beim Speichern und Laden der Datenbank)
+Added: GetRowPtr() gibt den Pointer der aktuellen oder genannten Zeile zurück
+Added: Eine Fehlermeldung kam durch SelectEntryByPtr() hinzu
+Added: Fehlermeldung #DB_RowNotFound: Wenn ein ungültiger Zeilenpointer übergeben wurde, kann es dazu kommen
+Changed: Jede Funktion nochmal umgekrempelt (viele Änderungen -> viele neue Bugs?)
+Bisher schon 54 Funktionen (!)

Es kann sein, dass ich jetzt irgendwas vergessen hab bei der Auflistung.
(Ohje, ich freu mich schon darauf, die Hilfe zu machen... *puh*)

Die Hashfunktionen kommen bald, sie sind schon zur Hälfte fertig. Ich
werde sie außerdem noch in einem neuen Thread einzeln vorstellen. Aber
die Datenbank wird sie verwenden.

Hier wieder der Quellcode als Link:
DB.pbi (Die Datenbank als Include)
CompareWithWildcards.pbi (Die Include für die Datenbank)

Und wieder ein Beispielcode!
Zuletzt geändert von NicTheQuick am 18.10.2006 20:26, insgesamt 19-mal geändert.
FloHimself
Beiträge: 338
Registriert: 05.09.2004 18:47

Beitrag von FloHimself »

Saubere Arbeit NTQ! Kann ich gut gebrauchen. :allright:

Gleich ein Feature-Request? Speichern in und Lesen aus Datei. :wink:
FloHimself
Beiträge: 338
Registriert: 05.09.2004 18:47

Beitrag von FloHimself »

Fand die Datenbank so schön, da hab ich das Speichern und Laden
mal eingebaut. Die Funktionen kannst du noch vereinfachen. Habs
erstmal so gelassen, damit du den Source einfacher lesen kannst.
Sollte vielleicht noch etwas besser geprüft werden beim Import.

Einen Vorschlag noch:
Schöner wäre das Interface, wenn du den DB_ präfix entfernst,
da der Object-Typ ja bekannt ist.
Beispiel: *DB\DB_AddLine zu *DB\AddLine()

Code: Alles auswählen

Interface DB 
  DB_GetName.s() 
  DB_SetName.l(Name.s) 
  
  DB_AddRow.l(Typ.l, Name.s) 
  DB_SetRowName.l(Name.s, NewName.s) 
  DB_SetRowNameByID.l(ID.l, NewName.s) 
  DB_GetRowNameByID.s(ID.l) 
  DB_GetRowIDByName.l(Name.s) 
  DB_GetRowTyp.l(Name.s) 
  DB_GetRowTypByID.l(ID.l) 
  DB_CountRows.l() 
  
  DB_AddLine.l() 
  DB_SelectLine.l(Line.l) 
  DB_SelectLineByEntryPtr.l(Row.l, *Entry) 
  DB_SelectLineByEntry.l(Row.l, Entry.l) 
  DB_SelectLineByEntryS.l(Row.l, Entry.s) 
  DB_FirstLine.l() 
  DB_LastLine.l() 
  DB_NextLine.l() 
  DB_PrevLine.l() 
  DB_GetSelectedLine.l() 
  DB_ClearLine.l() 
  DB_DeleteLine.l() 
  DB_CountLines.l() 
  
  DB_SetEntryPtr.l(Row.l, *Entry) 
  DB_SetEntry.l(Row.l, Entry.l) 
  DB_SetEntryS.l(Row.l, Entry.s) 
  DB_GetEntry.l(Row.l) 
  DB_GetEntryS.s(Row.l)
  
  DB_DumpToFile(FileName.s)
EndInterface 

Structure DB_Entry 
  StructureUnion 
    b.b 
    w.w 
    l.l 
    f.f 
    s.s 
  EndStructureUnion 
EndStructure 

#DB_ChunkLines = 128 

;Einträge:  4 Bytes, Spaltenanzahl 
;          xx Bytes, Daten 

Structure DB_Struc 
  VTable.l 
  
  ;Functions 
  fDB_GetName.l 
  fDB_SetName.l 
  
  fDB_AddRow.l 
  fDB_SetRowName.l 
  fDB_SetRowNameByID.l 
  fDB_GetRowNameByID.l 
  fDB_GetRowIDByName.l 
  fDB_GetRowTyp.l 
  fDB_GetRowTypByID.l 
  fDB_CountRows.l 
  
  fDB_AddLine.l 
  fDB_SelectLine.l 
  fDB_SelectLineByEntryPtr.l 
  fDB_SelectLineByEntry.l 
  fDB_SelectLineByEntryS.l 
  fDB_FirstLine.l 
  fDB_LastLine.l 
  fDB_NextLine.l 
  fDB_PrevLine.l 
  fDB_GetSelectedLine.l 
  fDB_ClearLine.l 
  fDB_DeleteLine.l 
  fDB_CountLines.l 
  
  fDB_SetEntryPtr.l 
  fDB_SetEntry.l 
  fDB_SetEntryS.l 
  fDB_GetEntry.l 
  fDB_GetEntryS.l 
  
  fDB_DumpToFile.l
  
  ;Data 
  Name.s              ; Name der Datenbank 
  Rows.l              ; Anzahl an Spalten 
  Lines.l             ; Anzahl an Einträgen 
  ActLine.l           ; aktuelle Zeile 
  SortedRow.l         ; Spalte, die sortiert ist zum schnelleren finden 
  *pRowName.STRING    ; Namen der Spaltenheader (Size = 4 * Rows) 
  *pRowTyp            ; Typen der Spaltenheader (Size = Rows) 
  *pLines             ; Zeilen der Datenbank, enthalten Pointer zu den Einträgen (Size = Lines * 4 * Rows) 
EndStructure 

Procedure.s DB_GetName(*DB.DB_Struc) ;Gibt den Namen der Datenbank zurück 
  If *DB 
    ProcedureReturn *DB\Name 
  EndIf 
  ProcedureReturn "" 
EndProcedure 
Procedure.l DB_SetName(*DB.DB_Struc, Name.s) ;Setzt den Namen der Datenbank 
  If *DB 
    *DB\Name = Name 
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
;- Break 
Procedure.l DB_AddRow(*DB.DB_Struc, Typ.l, Name.s) ;Fügt eine neue Spalte hinzu 
  Protected Result1.l, Result2.l, *s.STRING 
  
  If *DB 
    Typ = Typ & $FF 
    
    ; Erste Spalte hinzufügen 
    If *DB\Rows = 0 
      *DB\pRowTyp = AllocateMemory(1) 
      If *DB\pRowTyp = 0 : ProcedureReturn #False : EndIf 
      *DB\pRowName = AllocateMemory(SizeOf(STRING)) 
      If *DB\pRowName = 0 : FreeMemory(*DB\pRowTyp) : ProcedureReturn #False : EndIf 
      
      *DB\Rows = 1 
      
      PokeB(*DB\pRowTyp, Typ) 
      *DB\pRowName\s = Name 
      
      ProcedureReturn #True 
      
      ; Weitere Spalten hinzufügen 
    Else 
      Result1 = ReAllocateMemory(*DB\pRowTyp, *DB\Rows + 1) 
      If Result1 = 0 : ProcedureReturn #False : EndIf 
      Result2 = ReAllocateMemory(*DB\pRowName, *DB\Rows * 4 + 4) 
      
      If Result2 
        *DB\Rows + 1 
        *DB\pRowTyp = Result1 
        *DB\pRowName = Result2 
        
        *s = Result2 + *DB\Rows * 4 - 4 
        *s\s = Name 
        PokeB(*DB\pRowTyp + *DB\Rows - 1, Typ) 
        ProcedureReturn #True 
      EndIf 
    EndIf  
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_SetRowName(*DB.DB_Struc, Name.s, NewName.s) 
  Protected a.l, *s.STRING 
  
  If *DB 
    *s = *DB\pRowName 
    For a = 1 To *DB\Rows 
      If *s\s = Name 
        *s\s = NewName 
        ProcedureReturn #True 
      EndIf 
      *s + SizeOf(STRING) 
    Next 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_SetRowNameByID(*DB.DB_Struc, ID.l, NewName.s) 
  Protected *s.STRING 
  
  If *DB 
    If ID <= 0 Or ID > *DB\Rows : ProcedureReturn #False : EndIf 
    *s = *DB\pRowName + ID * 4 - 4 
    *s\s = NewName 
    ProcedureReturn #True 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.s DB_GetRowNameByID(*DB.DB_Struc, ID.l) 
  Protected *s.STRING 
  
  If *DB 
    If ID <= 0 Or ID > *DB\Rows : ProcedureReturn "" : EndIf 
    *s = *DB\pRowName + ID * 4 - 4 
    ProcedureReturn *s\s 
  EndIf 
  
  ProcedureReturn "" 
EndProcedure 
Procedure.l DB_GetRowIDByName(*DB.DB_Struc, Name.s) 
  Protected a.l, *s.STRING 
  
  If *DB 
    *s = *DB\pRowName 
    For a = 1 To *DB\Rows 
      If *s\s = Name : Break : EndIf 
      *s + SizeOf(STRING) 
    Next 
    If a <= *DB\Rows : ProcedureReturn a : EndIf 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_GetRowTyp(*DB.DB_Struc, Name.s) 
  Protected a.l, *s.STRING 
  
  If *DB 
    *s = *DB\pRowName 
    For a = 1 To *DB\Rows 
      If *s\s = Name : Break : EndIf 
      *s + SizeOf(STRING) 
    Next 
    If a <= *DB\Rows 
      ProcedureReturn PeekB(*DB\pRowTyp + a - 1) & $FF 
    EndIf 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_GetRowTypByID(*DB.DB_Struc, ID.l) 
  Protected *s.STRING 
  
  If *DB 
    If ID <= 0 Or ID > *DB\Rows : ProcedureReturn #False : EndIf 
    ProcedureReturn PeekB(*DB\pRowTyp + ID -1) & $FF 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_CountRows(*DB.DB_Struc) 
  If *DB 
    ProcedureReturn *DB\Rows 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
;- Break 
Procedure.l DB_AddLine(*DB.DB_Struc) 
  Protected *Line.LONG 
  If *DB 
    If *DB\Rows = 0 : ProcedureReturn #False : EndIf 
    If *DB\Lines = 0 
      *DB\pLines = AllocateMemory(#DB_ChunkLines * 4) 
      If *DB\pLines = 0 : ProcedureReturn #False : EndIf 
      
      *Line = *DB\pLines 
      *Line\l = AllocateMemory(*DB\Rows * 4 + 4) 
      PokeL(*Line\l, *DB\Rows) 
      *DB\Lines = 1 
      *DB\ActLine = 1 
      ProcedureReturn #True 
      
    Else 
      If (*DB\Lines + 1) % #DB_ChunkLines = 0 
        *DB\pLines = ReAllocateMemory(*DB\pLines, (*DB\Lines + 1) / #DB_ChunkLines * 100) 
        If *DB\pLines = 0 : ProcedureReturn #False : EndIf 
      EndIf 
      *DB\Lines + 1 
      *Line = *DB\pLines + *DB\Lines * 4 - 4 
      *Line\l = AllocateMemory(*DB\Rows * 4 + 4) 
      PokeL(*Line\l, *DB\Rows) 
      *DB\ActLine = *DB\Lines 
    EndIf 
    
  EndIf 
EndProcedure 
Procedure.l DB_SelectLine(*DB.DB_Struc, Line.l) 
  If *DB 
    If Line > 0 And Line <= *DB\Lines 
      *DB\ActLine = Line 
    EndIf 
  EndIf 
EndProcedure 
Procedure.l DB_SelectLineByEntryPtr(*DB.DB_Struc, Row.l, *Entry.DB_Entry) 
  Protected a.l, *Line.LONG, *vEntry.DB_Entry, Typ.l, Offset.l 
  
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines 
      Offset = Row * 4 
      
      For a = 1 To *DB\Lines 
        *vEntry = *Line\l + Offset 
        If PeekL(*Line\l) >= Row 
          Select Typ 
            Case 'b' : If *vEntry\b = *Entry\b : Break : EndIf 
            Case 'w' : If *vEntry\w = *Entry\w : Break : EndIf 
            Case 'l' : If *vEntry\l = *Entry\l : Break : EndIf 
            Case 'f' : If *vEntry\f = *Entry\f : Break : EndIf 
            Case 's' : If *vEntry\s = PeekS(*Entry) : Break : EndIf 
          EndSelect 
        EndIf 
        *Line + 4 
      Next 
      
      If a <= *DB\Lines 
        *DB\ActLine = a 
        ProcedureReturn #True 
      EndIf 
      
    EndIf 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_SelectLineByEntry(*DB.DB_Struc, Row.l, Entry.l) 
  Protected a.l, *Line.LONG, *vEntry.DB_Entry, Typ.l, Offset.l 
  
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines 
      Offset = Row * 4 
      
      For a = 1 To *DB\Lines 
        *vEntry = *Line\l + Offset 
        If PeekL(*Line\l) >= Row 
          Select Typ 
            Case 'b' : If *vEntry\b = Entry : Break : EndIf 
            Case 'w' : If *vEntry\w = Entry : Break : EndIf 
            Case 'l' : If *vEntry\l = Entry : Break : EndIf 
            Case 'f' : If *vEntry\f = Entry : Break : EndIf 
            Case 's' : If *vEntry\s = Str(Entry) : Break : EndIf 
          EndSelect 
        EndIf 
        *Line + 4 
      Next 
      
      If a <= *DB\Lines 
        *DB\ActLine = a 
        ProcedureReturn #True 
      EndIf 
      
    EndIf 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_SelectLineByEntryS(*DB.DB_Struc, Row.l, Entry.s) 
  Protected a.l, *Line.LONG, *vEntry.DB_Entry, Typ.l, Offset.l 
  
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines 
      Offset = Row * 4 
      
      For a = 1 To *DB\Lines 
        *vEntry = *Line\l + Offset 
        If PeekL(*Line\l) >= Row 
          Select Typ 
            Case 'b' : If *vEntry\b = Val(Entry) : Break : EndIf 
            Case 'w' : If *vEntry\w = Val(Entry) : Break : EndIf 
            Case 'l' : If *vEntry\l = Val(Entry) : Break : EndIf 
            Case 'f' : If *vEntry\f = ValF(Entry) : Break : EndIf 
            Case 's' : If *vEntry\s = Entry : Break : EndIf 
          EndSelect 
        EndIf 
        *Line + 4 
      Next 
      
      If a <= *DB\Lines 
        *DB\ActLine = a 
        ProcedureReturn #True 
      EndIf 
      
    EndIf 
  EndIf 
  
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_FirstLine(*DB.DB_Struc) 
  If *DB 
    If *DB\Lines = 0 : ProcedureReturn #False : EndIf 
    *DB\ActLine = 1 
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_LastLine(*DB.DB_Struc) 
  If *DB 
    If *DB\Lines = 0 : ProcedureReturn #False : EndIf 
    *DB\ActLine = *DB\Lines 
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_NextLine(*DB.DB_Struc) 
  If *DB 
    If *DB\ActLine < *DB\Lines 
      *DB\ActLine + 1 
      ProcedureReturn #True 
    EndIf 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_PrevLine(*DB.DB_Struc) 
  If *DB 
    If *DB\ActLine > 1 
      *DB\ActLine - 1 
      ProcedureReturn #True 
    EndIf 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_GetSelectedLine(*DB.DB_Struc) 
  If *DB 
    ProcedureReturn *DB\ActLine 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_ClearLine(*DB.DB_Struc) 
  Protected *Line.LONG, *Entry.DB_Entry, *Typ.BYTE, Row.l, Rows.l 
  If *DB 
    *Line = *DB\pLines + *DB\ActLine * 4 - 4 
    *Entry = *Line\l + 4 
    Rows = PeekL(*Line\l) 
    *Typ = *DB\pRowTyp 
    For Row = 1 To Rows 
      Select *Typ\b 
        Case 'b' : *Entry\b = 0 
        Case 'w' : *Entry\w = 0 
        Case 'l' : *Entry\l = 0 
        Case 'f' : *Entry\f = 0 
        Case 's' : *Entry\s = "" 
      EndSelect 
      *Typ + 1 
      *Entry + 4 
    Next 
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_DeleteLine(*DB.DB_Struc) 
  Protected *Line.LONG, *Entry.DB_Entry, *Typ.BYTE, Row.l, Rows.l 
  If *DB 
    *Line = *DB\pLines + *DB\ActLine * 4 - 4 
    *Entry = *Line\l + 4 
    Rows = PeekL(*Line\l) 
    *Typ = *DB\pRowTyp 
    For Row = 1 To Rows 
      If *Typ\b = 's' :*Entry\s = "" : EndIf 
      *Typ + 1 
      *Entry + 4 
    Next 
    FreeMemory(*Line\l) 
    CopyMemory(*DB\pLines + *DB\ActLine * 4, *DB\pLines + *DB\ActLine * 4 - 4, (*DB\Lines - *DB\ActLine) * 4) 
    ReAllocateMemory(*DB\pLines, *DB\Lines * 4 - 4) 
    *DB\Lines - 1 
    If *DB\ActLine > *DB\Lines : *DB\ActLine = *DB\Lines : EndIf 
  EndIf 
EndProcedure 
Procedure.l DB_CountLines(*DB.DB_Struc) 
  If *DB 
    ProcedureReturn *DB\Lines 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
;- Break 
Procedure.l DB_SetEntryPtr(*DB.DB_Struc, Row.l, *Entry.DB_Entry) 
  Protected Typ.l, *vEntry.DB_Entry, *Line.LONG, Result1.l 
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines + *DB\ActLine * 4 - 4 
      
      If PeekL(*Line\l) < Row 
        Result1 = ReAllocateMemory(*Line\l, *DB\Rows * 4 + 4) 
        If Result1 = 0 : ProcedureReturn #False : EndIf 
        *Line\l = Result1 
      EndIf 
      
      *vEntry = *Line\l + Row * 4 
      Select Typ 
        Case 'b' : *vEntry\b = *Entry\b 
        Case 'w' : *vEntry\w = *Entry\w 
        Case 'l' : *vEntry\l = *Entry\l 
        Case 'f' : *vEntry\f = *Entry\f 
        Case 's' : *vEntry\s = PeekS(*Entry) 
      EndSelect 
      ProcedureReturn #True 
    EndIf 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_SetEntry(*DB.DB_Struc, Row.l, Entry.l) 
  Protected Typ.l, *vEntry.DB_Entry, *Line.LONG, Result1.l 
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines + *DB\ActLine * 4 - 4 
      
      If PeekL(*Line\l) < Row 
        Result1 = ReAllocateMemory(*Line\l, *DB\Rows * 4 + 4) 
        If Result1 = 0 : ProcedureReturn #False : EndIf 
        *Line\l = Result1 
      EndIf 
      
      *vEntry = *Line\l + Row * 4 
      Select Typ 
        Case 'b' : *vEntry\b = Entry & $FF 
        Case 'w' : *vEntry\w = Entry & $FFFF 
        Case 'l' : *vEntry\l = Entry 
        Case 'f' : *vEntry\f = Entry 
        Case 's' : *vEntry\s = Str(Entry) 
      EndSelect 
      ProcedureReturn #True 
    EndIf 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_SetEntryS(*DB.DB_Struc, Row.l, Entry.s) 
  Protected Typ.l, *vEntry.DB_Entry, *Line.LONG, Result1.l 
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines + *DB\ActLine * 4 - 4 
      
      If PeekL(*Line\l) < Row 
        Result1 = ReAllocateMemory(*Line\l, *DB\Rows * 4 + 4) 
        If Result1 = 0 : ProcedureReturn #False : EndIf 
        *Line\l = Result1 
      EndIf 
      
      *vEntry = *Line\l + Row * 4 
      Select Typ 
        Case 'b' : *vEntry\b = Val(Entry) 
        Case 'w' : *vEntry\w = Val(Entry) 
        Case 'l' : *vEntry\l = Val(Entry) 
        Case 'f' : *vEntry\f = ValF(Entry) 
        Case 's' : *vEntry\s = Entry 
      EndSelect 
      ProcedureReturn #True 
    EndIf 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.l DB_GetEntry(*DB.DB_Struc, Row.l) 
  Protected Typ.l, *vEntry.DB_Entry, *Line.LONG 
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines + *DB\ActLine * 4 - 4 
      
      If PeekL(*Line\l) < Row : ProcedureReturn #False : EndIf 
      
      *vEntry = *Line\l + Row * 4 
      Select Typ 
        Case 'b' : ProcedureReturn *vEntry\b 
        Case 'w' : ProcedureReturn *vEntry\w 
        Case 'l' : ProcedureReturn *vEntry\l 
        Case 'f' : ProcedureReturn *vEntry\f 
        Case 's' : ProcedureReturn *vEntry 
      EndSelect 
      ProcedureReturn #True 
    EndIf 
  EndIf 
  ProcedureReturn #False 
EndProcedure 
Procedure.s DB_GetEntryS(*DB.DB_Struc, Row.l) 
  Protected Typ.l, *vEntry.DB_Entry, *Line.LONG 
  If *DB 
    If Row > 0 And Row <= *DB\Rows 
      Typ = PeekB(*DB\pRowTyp + Row - 1) & $FF 
      *Line = *DB\pLines + *DB\ActLine * 4 - 4 
      
      If PeekL(*Line\l) < Row : ProcedureReturn "" : EndIf 
      
      *vEntry = *Line\l + Row * 4 
      Select Typ 
        Case 'b' : ProcedureReturn Str(*vEntry\b) 
        Case 'w' : ProcedureReturn Str(*vEntry\w) 
        Case 'l' : ProcedureReturn Str(*vEntry\l) 
        Case 'f' : ProcedureReturn StrF(*vEntry\f) 
        Case 's' : ProcedureReturn *vEntry\s 
      EndSelect 
    EndIf 
  EndIf 
  ProcedureReturn "" 
EndProcedure
;- Break
Procedure.l DB_DumpToFile(*DB.DB_Struc, FileName.s)
  Protected DB_File.l
  DB_File = CreateFile(#PB_Any, FileName)
  If DB_File
    WriteStringN(DB_GetName(*DB))
    For i=1 To *DB\Rows
      WriteString(DB_GetRowNameByID(*DB, i) + ".")
      WriteByte(DB_GetRowTypByID(*DB, i))
      If i <> *DB\Rows 
        WriteString("|")
      Else
        WriteStringN("")
      EndIf
    Next
    If DB_FirstLine(*DB) 
      Repeat 
        For i=1 To *DB\Rows
          WriteString(DB_GetEntryS(*DB, i))
          If i <> *DB\Rows
            WriteString("|")
          Else
            WriteStringN("")
          EndIf
        Next
      Until DB_NextLine(*DB) = #False 
    EndIf
    CloseFile(DB_File)
    ProcedureReturn #True
  EndIf
  ProcedureReturn #False
EndProcedure
Declare DB_Create(Name.s)
Procedure DB_CreateFromDump(FileName.s)
  Protected *DB.DB_Struc, DB_File.l, Rows.l, TableDesc.s, RowDesc.s, RowName.s, RowType.s, LineOfData.s
  DB_File = OpenFile(#PB_Any, FileName)
  If DB_File
    *DB.DB_Struc = DB_Create(ReadString())
    TableDesc = ReadString()
    Rows = CountString(TableDesc, "|") + 1
    For i = 1 To Rows
      RowDesc = StringField(TableDesc, i, "|")
      RowName = StringField(RowDesc, 1, ".")
      RowType = StringField(RowDesc, 2, ".")
      DB_AddRow(*DB, Asc(RowType), RowName) 
    Next
    While Eof(DB_File) = 0
      LineOfData = ReadString()
      DB_AddLine(*DB) 
      For i = 1 To Rows
        DB_SetEntryS(*DB, i, StringField(LineOfData, i, "|"))
      Next
    Wend
    CloseFile(DB_File)
    ProcedureReturn *DB
  EndIf
  ProcedureReturn #False
EndProcedure

;- Break 
Procedure.l DB_Create(Name.s) 
  Protected *DB.DB_Struc 
  
  *DB = AllocateMemory(SizeOf(DB_Struc)) 
  *DB\VTable = *DB + 4 
  
  *DB\fDB_GetName               = @DB_GetName() 
  *DB\fDB_SetName               = @DB_SetName() 
  
  *DB\fDB_AddRow                = @DB_AddRow() 
  *DB\fDB_SetRowName            = @DB_SetRowName() 
  *DB\fDB_SetRowNameByID        = @DB_SetRowNameByID() 
  *DB\fDB_GetRowNameByID        = @DB_GetRowNameByID() 
  *DB\fDB_GetRowIDByName        = @DB_GetRowIDByName() 
  *DB\fDB_GetRowTyp             = @DB_GetRowTyp() 
  *DB\fDB_GetRowTypByID         = @DB_GetRowTypByID() 
  *DB\fDB_CountRows             = @DB_CountRows() 
  
  *DB\fDB_AddLine               = @DB_AddLine() 
  *DB\fDB_SelectLine            = @DB_SelectLine() 
  *DB\fDB_SelectLineByEntryPtr  = @DB_SelectLineByEntryPtr() 
  *DB\fDB_SelectLineByEntry     = @DB_SelectLineByEntry() 
  *DB\fDB_SelectLineByEntryS    = @DB_SelectLineByEntryS() 
  *DB\fDB_FirstLine             = @DB_FirstLine() 
  *DB\fDB_LastLine              = @DB_LastLine() 
  *DB\fDB_NextLine              = @DB_NextLine() 
  *DB\fDB_PrevLine              = @DB_PrevLine() 
  *DB\fDB_GetSelectedLine       = @DB_GetSelectedLine() 
  *DB\fDB_ClearLine             = @DB_ClearLine() 
  *DB\fDB_DeleteLine            = @DB_DeleteLine() 
  *DB\fDB_CountLines            = @DB_CountLines() 
  
  *DB\fDB_SetEntryPtr           = @DB_SetEntryPtr() 
  *DB\fDB_SetEntry              = @DB_SetEntry() 
  *DB\fDB_SetEntryS             = @DB_SetEntryS() 
  *DB\fDB_GetEntry              = @DB_GetEntry() 
  *DB\fDB_GetEntryS             = @DB_GetEntryS()
  
  *DB\fDB_DumpToFile            = @DB_DumpToFile()
  
  If *DB = 0 : ProcedureReturn #False : EndIf 
  *DB\Name = Name 
  *DB\Rows = 0 
  *DB\Lines = 0 
  *DB\ActLine = 0 
  *DB\SortedRow = 0 
  
  ProcedureReturn *DB 
EndProcedure 

;Neue Datenbank erstellen 
*DB.DB = DB_Create("Personen") 
*DB\DB_AddRow('l', "ID") 
*DB\DB_AddRow('s', "Vorname") 
*DB\DB_AddRow('s', "Nachname") 

;Datenbank namen ermitteln 
Debug "Datenbank: " + *DB\DB_GetName() 

;Anzahl an Spalten ausgeben 
Debug "Spalten: " + Str(*DB\DB_CountRows()) 

;Spaltennamen auflisten 
For a = 1 To *DB\DB_CountRows() 
  Debug Str(a) + " - " + *DB\DB_GetRowNameByID(a) 
Next 

;Erstelle fünf Einträge 
*DB\DB_AddLine() 
*DB\DB_SetEntry(1, 1) 
*DB\DB_SetEntryS(2, "Thomas") 
*DB\DB_SetEntryS(3, "Henry") 
*DB\DB_AddLine() 
*DB\DB_SetEntry(1, 2) 
*DB\DB_SetEntryS(2, "Jessica") 
*DB\DB_SetEntryS(3, "Alba") 
*DB\DB_AddLine() 
*DB\DB_SetEntry(1, 3) 
*DB\DB_SetEntryS(2, "Tom") 
*DB\DB_SetEntryS(3, "Wichtigtuer") 
*DB\DB_AddLine() 
*DB\DB_SetEntry(1, 4) 
*DB\DB_SetEntryS(2, "Franz") 
*DB\DB_SetEntryS(3, "Guckindieluft") 
*DB\DB_AddLine() 
*DB\DB_SetEntry(1, 5) 
*DB\DB_SetEntryS(2, "Sebastian") 
*DB\DB_SetEntryS(3, "Koch") 

Debug "Ausgabe:"
;Gib die Anzahl der Einträge aus 
Debug "Einträge: " + Str(*DB\DB_CountLines()) 

Debug "" 
;Such den Eintrag mit Vornamen 'Jessica" 
*DB\DB_SelectLineByEntryS(2, "Jessica") 

;Gib ID und Nachname aus 
Debug "ID: " + *DB\DB_GetEntryS(1) 
Debug "Nachname: " + *DB\DB_GetEntryS(3) 

Debug "" 
;Such den Eintrag mit ID '4' 
*DB\DB_SelectLineByEntry(1, 4) 

;Gib Vorname und Nachname aus: 
Debug "Vorname: " + *DB\DB_GetEntryS(2) 
Debug "Nachname: " + *DB\DB_GetEntryS(3) 

Debug "" 
;Leere die aktuelle Zeile 
*DB\DB_ClearLine() 

;Gib ID, Vorname und Nachname nochmal aus 
Debug "ID: " + *DB\DB_GetEntryS(1) 
Debug "Vorname: " + *DB\DB_GetEntryS(2) 
Debug "Nachname: " + *DB\DB_GetEntryS(3) 

Debug "" 
;Lösche den Eintrag komplett 
*DB\DB_DeleteLine() 

;Liste nochmal die komplette Tabelle auf 
If *DB\DB_FirstLine() 
  Repeat 
    Debug *DB\DB_GetEntryS(1) + ", " + *DB\DB_GetEntryS(2) + ", " + *DB\DB_GetEntryS(3) 
  Until *DB\DB_NextLine() = #False 
EndIf

;Datenbank in Datei dumpen
*DB\DB_DumpToFile("C:\database.dump")

;Datenbank wieder einlesen
*DB = DB_CreateFromDump("C:\database.dump")

Debug "" 
;Liste importierte Tabelle auf 
If *DB\DB_FirstLine() 
  Repeat 
    Debug *DB\DB_GetEntryS(1) + ", " + *DB\DB_GetEntryS(2) + ", " + *DB\DB_GetEntryS(3) 
  Until *DB\DB_NextLine() = #False 
EndIf
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Das mit dem Speichern und Lesen der Datenbank war schon auf meiner
ToDo-Liste. Ich werde wahrscheinlich die Funktionen dafür nochmal
komplett umschreiben, weil meine wesentlich schneller sein werden als
deine. Schließlich überblicke ich meinen Code bisher wahrscheinlich noch
mehr als du. <)

Aber nichts für Ungut! Ich bin froh über die positive Resonanz. :allright:
Danke!
Benutzeravatar
helpy
Beiträge: 636
Registriert: 29.08.2004 13:29

Beitrag von helpy »

Nic!
Gefällt mir sehr gut!

Eine Frage zur Terminologie: "Warum verwendest Du die Begriffe Row und Lines?" Row wird doch in bezug auf Datenbanken und Tabellen immer für eine Zeile verwendet, kannte das bisher nicht anders! Von der Begrifflichkeit her wäre es gerade für die engl. sprachigen Leute besser statt Row => Col (Column) und statt Line => Row zu verwenden.

cu, helpy
Zuletzt geändert von helpy am 12.09.2005 08:24, insgesamt 1-mal geändert.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

@helpy:
Hm... Da hast du natürlich Recht.
Ich bin mit der Sache nicht so vertraut, dass ich das alles richtig mache.

Ich werde mich demnächst mal dransetzen und alles entsprechenden umbenennen.
Danke für den Hinweis.
Benutzeravatar
Kiffi
Beiträge: 10714
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Beitrag von Kiffi »

@Nic: Saubere Arbeit :allright: Ich werde mich mal gleich an's Konvertieren machen.

@FloHimself: Danke für Deine Dump-Routinen! Allerdings würde ich hier
vorschlagen, einen anderen Separator als das Pipe ('|') zu verwenden, weil es
durchaus vorkommen kann, dass dieses Zeichen in einem der
Datenbankfelder vorkommen kann. Mein Vorschlag wäre, das Trennzeichen
beim Aufruf der Prozedur frei bestimmen zu dürfen.

Code: Alles auswählen

Procedure.l DB_DumpToFile(*DB.DB_Struc, FileName.s, sSeparator.s)
Dann könnte man den Separator selber auch in den Dump speichern, so dass
man ihn beim [c]DB_CreateFromDump()[/c] nicht mehr mit angeben muss.

Grüße ... Kiffi
a²+b²=mc²
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

@Kiffi:
Ich schreibe die Dump-Routinen nochmal neu. Ich mag nämlich keine
Separatoren und schon gar nicht das Auslesen eines Strings mit
Terminierung.

Also warte bitte noch mit diesen Routinen oder benutze sie nur vorläufig,
so dass man sie schnell ändern kann, wenn ich so weit bin. Das wird wohl
noch bis morgen dauern, da ich heute bis 16:50 Uhr Schule habe und den
Rest des Tages mit wichtigeren Dingen zu tun habe.

Bis dann also.

///Edit 1: Es hat sich was getan. Alles weitere steht nochmal im ersten Post unter dem "///Edit 1:".

///Edit 2: Es hat sich schon wieder etwas getan. Alles weitere steht nochmal im ersten Post unter dem "///Edit 2:".

Die vorläufige ToDo-Liste ist klein:
  • Indizierung einer oder mehrerer Spalten zum schnelleren Durchsuchen
  • Hinzufügen einzelner Einträge auch mittenrein, also nicht nur ans Ende
  • Löschen und Hinzufügen einzelner Spalten auch mittendrin (Eine solche Aktion würde aber länger dauern)
Neue Vorschläge und Ideen sind gerne willkommen!
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Das mit der Indizierung der Spalten zum schnelleren Durchsuchen ist
schon in vollem Gange.

Da dies dann ein einzelnes laufbares Interface werden wird, das lediglich
von dem Datenbank-Interface benutzt wird, werde ich dazu wohl auch
noch einen neuen Thread aufmachen, bei dem mir dann ein paar Tester
sagen können, welchen Geschwindigkeitsschub das Ganze bringen wird.

Eins kann ich ja schonmal verraten:
Das schnelle Zuordnen einer Zahl (z.B. ein Pointer) zu einem String wird
mittels eines MD5-Fingerprints erreicht, der geschickt ausgewertet wird.
Näheres dazu aber erst später, wenns fertig ist.
Benutzeravatar
dysti
Beiträge: 656
Registriert: 10.02.2006 18:34
Wohnort: Schlicktown

Beitrag von dysti »

Ist das Projekt noch am laufen. Wurden Veränderungen gemacht?
Antworten