Redundante SQLite Datenbank

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
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Redundante SQLite Datenbank

Beitrag von mk-soft »

Brauchte für ein Projekt eine redundante Datenbank falls einer der Datenträger ausfallen sollte.

Es gibt nur folgende Funktionen
- RedInitDatabase(...)
Öffnet oder erstellt die Datenbanken
- RedCloseDatabase(...)
Schließt die Datenbanken
- RedDatabaseUpdate(...)
Schreib auf beide Datenbanken
- RedDatabaseQuery(...)
Liest von der primären Datenbank

Update v1.03
Neu Funktionen
- RedDatabaseError()
Letzter Fehler auslesen
- RedDatabaseSync(...)
Synconisierung der Datenbanken nach einen Crash

Hinweis:
Ein Query wird nur auf der primären Datenbank ausgeführt.

Sonderfall:
Ein "delete from" muss wohl über ein "DatabaseQuery" ausgeführt werden. Warum weiss ich nicht.
Hier für auch die Funktion "RedDatabaseUpdate" verwenden. Intern wird bei "delete from" statt "DatabaseUpdate" ein "DatabaseQuery" ausgeführt.

Empfehlung:
Die Funktion RedDatabaseSync(...) nicht automatisch ausführen. Es sollte erst von der erhalteten Datenbank nach einen Systemcrash eine Sicherungskopie erstellt werden. Dann erst mit RedInitDatabase(..., createdatabase) die fehlende Datenbank erstellen lassen, die Tabellen anlegen und mit RedDatabaseSync(...) den abgleich durchführen.

Todo:
Umstellung von "Begin, Commit, Rollback" auf "Savepoint ..., Release, Rollback to ..."
Die Funktion "Savepoint" funktioniert irgendwie nicht. Weiss jemand rat?

Code: Alles auswählen

;-TOP
; ***************************************************************************************

; Comment       : Redundante SQLite Datenbank
; Author        : mk-soft, Germany
; Second Author : 
; File          : *.pb
; Version       : 1.03
; Create        : 16.03.2014
; Update        : 05.04.2014

EnableExplicit

UseSQLiteDatabase()

Define red_error.s

Procedure RedInitDatabase(db, path1.s, path2.s, filename.s, createdatabase = 0)
  
  Protected db1, db2, filename1.s, filename2.s , filepart.s, extensionpart.s , file, result
  
  ; Path korrigieren
  If Right(path1, 1) <> "\"
    path1 + "\"
  EndIf
  
  If Right(path2, 1) <> "\"
    path2 + "\"
  EndIf
  
  ; Datenbanknamen ertsellen
  filepart = GetFilePart(filename, #PB_FileSystem_NoExtension)
  extensionpart = GetExtensionPart(filename)
  filename1 = path1 + filepart + "_1." + extensionpart
  filename2 = path2 + filepart + "_2." + extensionpart
  
  If createdatabase
    If FileSize(filename1) <= 0
      file = CreateFile(#PB_Any, filename1)
      If file = 0
        ProcedureReturn 0
      Else
        CloseFile(file)
      EndIf
    EndIf
    If FileSize(filename2) <= 0
      file = CreateFile(#PB_Any, filename2)
      If file = 0
        ProcedureReturn 0
      Else
        CloseFile(file)
      EndIf
    EndIf
  EndIf
  
  db1 = db
  db2 = db + 1
  result = 0
  
  ; Datenbank 1 öffnen
  If OpenDatabase(db1, filename1, "", "", #PB_Database_SQLite)
    result + 1
  EndIf
  
  ; Datenbank 2 öffnen
  If OpenDatabase(db2, filename2, "", "", #PB_Database_SQLite)
    result + 1
  EndIf
  
  ProcedureReturn result
  
EndProcedure

; ***************************************************************************************

Procedure RedCloseDatabase(db)
  
  Protected db1, db2, result
  
  db1 = db
  db2 = db + 1
  
  If IsDatabase(db1)
    If CloseDatabase(db1)
      result + 1
    EndIf
  EndIf
  
  If IsDatabase(db2)
    If CloseDatabase(db2)
      result + 1
    EndIf
  EndIf
  
  ProcedureReturn result
  
EndProcedure

; ***************************************************************************************

Procedure RedDatabaseUpdate(db, sql.s, as_query = 0)
 
  Protected db1, db2
  Protected begin1, begin2, update1, update2, count1, count2 , rollback1, rollback2, commit1, commit2, result1, result2, delete_from
  Shared red_error.s
 
  db1 = db
  db2 = db + 1
 
  If IsDatabase(db1) = 0
    red_error = "ERROR DATABASE CONNECTION 1"
    ProcedureReturn -1
  EndIf
 
  If IsDatabase(db2) = 0
    red_error = "ERROR DATABASE CONNECTION 2"
    ProcedureReturn -2
  EndIf
 
  red_error = "OK"
  delete_from = FindString(sql, "delete from", 1, #PB_String_NoCase)
  
  Repeat
    ; Begin 1
    begin1 = DatabaseUpdate(db1, "BEGIN")
    If begin1 = 0
      red_error = "ERROR DATABASE 1: " + DatabaseError()
      Break
    EndIf
   
    ; Begin 2
    begin2 = DatabaseUpdate(db2, "BEGIN")
    If begin2 = 0
      red_error = "ERROR DATABASE 2: " + DatabaseError()
      Break
    EndIf
    
    ; Update 1
    If as_query Or delete_from
      update1 = DatabaseQuery(db1, sql)
      If update1 = 0
        red_error = "ERROR DATABASE UPDATE 1: " + DatabaseError()
        rollback1 = #True
        rollback2 = #True
        Break
      Else
        While NextDatabaseRow(db1)
          ; 
        Wend
        FinishDatabaseQuery(db1)
        count1 = 1
      EndIf
    Else
      update1 = DatabaseUpdate(db1, sql)
      If update1 = 0
        red_error = "ERROR DATABASE UPDATE 1: " + DatabaseError()
        rollback1 = #True
        rollback2 = #True
        Break
      Else
        count1 = AffectedDatabaseRows(db1)
      EndIf
    EndIf
   
    ; Update 2
    If as_query Or delete_from
      update2 = DatabaseQuery(db2, sql)
      If update2 = 0
        red_error = "ERROR DATABASE UPDATE 2: " + DatabaseError()
        rollback1 = #True
        rollback2 = #True
        Break
      Else
        While NextDatabaseRow(db2)
          ; 
        Wend
        FinishDatabaseQuery(db2)
        count2 = 1
      EndIf
    Else
      update1 = DatabaseUpdate(db2, sql)
      If update1 = 0
        red_error = "ERROR DATABASE UPDATE 2: " + DatabaseError()
        rollback1 = #True
        rollback2 = #True
        Break
      Else
        count2 = AffectedDatabaseRows(db2)
      EndIf
    EndIf
   
    ; Vergleiche Affected Database Rows
    If count1 <> count2
      rollback1 = #True
      rollback2 = #True
      red_error = "ERROR DATABASE UPDATE : Redundant data set differently"
      Break
    EndIf
   
  Until #True
  
  ; Commit 1
  If Not rollback1
    commit1 = DatabaseUpdate(db1, "COMMIT")
    If commit1 = 0
      red_error = "ERROR DATABASE RELEASE 1: " + DatabaseError()
      rollback1 = #True
      rollback2 = #True
    Else
      commit1 = AffectedDatabaseRows(db1)
    EndIf
  EndIf
  
  ; Commit 2
  If Not rollback2
    commit2 = DatabaseUpdate(db2, "COMMIT")
    If commit2 = 0
      red_error = "ERROR DATABASE RELEASE 2: " + DatabaseError()
      rollback1 = #True
      rollback2 = #True
    Else
      commit2 = AffectedDatabaseRows(db2)
    EndIf
  EndIf
  
  ; Rollback 1
  If rollback1
    result1 = DatabaseUpdate(db1, "ROLLBACK")
    If result1 = 0
      red_error = "ERROR DATABASE ROLLBACK 1: " + DatabaseError()
    Else
      result1 = AffectedDatabaseRows(db1)
    EndIf
  EndIf
  
  ; Rollback 2
  If rollback2
    result2 = DatabaseUpdate(db2, "ROLLBACK")
    If result2 = 0
      red_error = "ERROR DATABASE ROLLBACK 2: " + DatabaseError()
    Else
      result2 = AffectedDatabaseRows(db2)
    EndIf
  EndIf
  
  If rollback1 Or rollback2
    ProcedureReturn -1
  Else
    ProcedureReturn count1
  EndIf
 
EndProcedure

; ***************************************************************************************

Procedure RedDatabaseQuery(db, sql.s, List result.s())
  
  Protected db0, db1, db2
  Protected query, count, result, colum, index, row.s
  Shared red_error.s
  
  db1 = db
  db2 = db + 1
  
  ClearList(result())
  
  If IsDatabase(db1)
    db0 = db1
  ElseIf IsDatabase(db2)
    db0 = db2
  Else
    red_error = "ERROR DATABASE CONNECTION"
    ProcedureReturn 0
  EndIf
  
  red_error = "OK"
  
  Repeat
    ; Query
    query = DatabaseQuery(db0, sql)
    If query = 0
      red_error = "ERROR DATABASE: " + DatabaseError()
      result = -1
      Break
    Else
      colum = DatabaseColumns(db0) - 1
      While NextDatabaseRow(db0)
        row = ""    
        For index = 0 To colum
          row + #DQUOTE$ + GetDatabaseString(db0, index) + #DQUOTE$
          If index < colum
            row + ";"
          EndIf
        Next
        AddElement(result())
        result() = row
      Wend
      result = ListSize(result()) 
    EndIf
  Until #True
  
  ResetList(result())
  
  ProcedureReturn result
  
EndProcedure

; ***************************************************************************************

Procedure.s RedDatabaseError()
  
  Shared red_error
  
  ProcedureReturn red_error
  
EndProcedure

; ***************************************************************************************

Procedure RedDatabaseSync(db, table.s, primary_key.s, modified_date.s = "")
  
  Protected dbx, db1, db2
  Protected sql1.s, sql2.s, query1, query2 
  Protected primary_column, date_column, date1.s, date2.s, result, column, index
  Protected Dim columns.s(0)
  Protected Dim values.s(0)
  
  Shared red_error.s
  
  db1 = db
  db2 = db + 1
  
  If IsDatabase(db1) = 0
    red_error = "ERROR DATABASE CONNECTION 1"
    ProcedureReturn -1
  EndIf
 
  If IsDatabase(db2) = 0
    red_error = "ERROR DATABASE CONNECTION 2"
    ProcedureReturn -2
  EndIf
 
  red_error = "OK"
  
  ; Fehlende Datensätze anlegen
  For dbx = 1 To 2
    If dbx = 1
      ; Vergleich primären Datenbank zur sekundären Datenbank
      db1 = db
      db2 = db +1
    Else
      ; Vergleich sekundären Datenbank zur primären Datenbank
      db1 = db + 1
      db2 = db
    EndIf
    
    Repeat
      ; Step 1
      sql1 = "SELECT * FROM " + table + ";"
      query1 = DatabaseQuery(db1, sql1)
      If query1 = 0
        red_error = "ERROR DATABASE SYNC " + db1 + ": " + DatabaseError()
        result = -1
        Break
      Else
        column = DatabaseColumns(db1) - 1
        Dim columns(column)
        Dim values(column)
        For index = 0 To column
          columns(index) = DatabaseColumnName(db1, index)
        Next
        primary_column = DatabaseColumnIndex(db1, primary_key)
        If primary_column < 0
          red_error = "ERROR DATABASE SYNC " + db1 + ": Unknown primary key"
          result = -1
          FinishDatabaseQuery(db1)
          Break
        EndIf  
        While NextDatabaseRow(db1)
          For index = 0 To column
            values(index) = "'" + GetDatabaseString(db1, index) + "'"
          Next
          sql2 = "SELECT * FROM " + table + " WHERE " + columns(primary_column) + " = " + values(primary_column) + ";"
          query2 = DatabaseQuery(db2, sql2)
          If query2 = 0
            red_error = "ERROR DATABASE SYNC " + db2 + ": " + DatabaseError()
            result = -1
            Break 3
          EndIf
          If NextDatabaseRow(db2)
            FinishDatabaseQuery(db2)
          Else
            FinishDatabaseQuery(db2)
            sql2 = "INSERT INTO " + table + " ("
            For index = 0 To column
              sql2 + columns(index)
              If index < column
                sql2 + ","
              Else
                sql2 + ")"
              EndIf
            Next
            sql2 + " VALUES ("
            For index = 0 To column
              sql2 + values(index)
              If index < column
                sql2 + ","
              Else
                sql2 + ")"
              EndIf
            Next
            sql2 + ";"
            query2 = DatabaseUpdate(db2, sql2)
            If query2 = 0
              red_error = "ERROR DATABASE SYNC " + db1 + ": " + DatabaseError()
              result = -1
              Break 3
            Else
              result + AffectedDatabaseRows(db2)
            EndIf
          EndIf
        Wend
      EndIf
        
    Until #True
    
  Next
  
  
  If modified_date = ""
    ProcedureReturn result
  EndIf
  
  ; Zeitstämpel überprüfen
  For dbx = 1 To 2
    If dbx = 1
      ; Vergleich primären Datenbank zur sekundären Datenbank
      db1 = db
      db2 = db +1
    Else
      ; Vergleich sekundären Datenbank zur primären Datenbank
      db1 = db + 1
      db2 = db
    EndIf
    
    Repeat
      ; Step 1
      sql1 = "SELECT * FROM " + table + ";"
      query1 = DatabaseQuery(db1, sql1)
      If query1 = 0
        red_error = "ERROR DATABASE SYNC " + db1 + ": " + DatabaseError()
        result = -1
        Break
      Else
        column = DatabaseColumns(db1) - 1
        Dim columns(column)
        Dim values(column)
        For index = 0 To column
          columns(index) = DatabaseColumnName(db1, index)
        Next
        primary_column = DatabaseColumnIndex(db1, primary_key)
        If primary_column < 0
          red_error = "ERROR DATABASE SYNC " + db1 + ": Unknown primary key"
          result = -1
          FinishDatabaseQuery(db1)
          Break
        EndIf  
        date_column = DatabaseColumnIndex(db1, modified_date)
        If date_column < 0
          red_error = "ERROR DATABASE SYNC " + db1 + ": Unknown modify date column"
          result = -1
          FinishDatabaseQuery(db1)
          Break
        EndIf  
        While NextDatabaseRow(db1)
          For index = 0 To column
            values(index) = "'" + GetDatabaseString(db1, index) + "'"
          Next
          sql2 = "SELECT * FROM " + table + " WHERE " + columns(primary_column) + " = " + values(primary_column) + ";"
          query2 = DatabaseQuery(db2, sql2)
          If query2 = 0
            red_error = "ERROR DATABASE SYNC " + db2 + ": " + DatabaseError()
            result = -1
            Break 3
          EndIf
          If NextDatabaseRow(db2) = 0
            FinishDatabaseQuery(db2)
          Else
            date1 = values(date_column)
            date2 = "'" + GetDatabaseString(db2, date_column) + "'"
            FinishDatabaseQuery(db2)
            If date1 <= date2
              Continue
            EndIf
            sql2 = "UPDATE " + table + " SET "
            For index = 0 To column
              If index <> primary_column
                sql2 + columns(index) + " = " + values(index)
                If index < column
                  sql2 + ","
                EndIf
              EndIf
            Next
            sql2 + " WHERE " + columns(primary_column) + " = " + values(primary_column)
            query2 = DatabaseUpdate(db2, sql2)
            If query2 = 0
              red_error = "ERROR DATABASE SYNC " + db2 + ": " + DatabaseError()
              result = -1
              Break 3
            Else
              result + AffectedDatabaseRows(db2)
            EndIf
          EndIf
        Wend
      EndIf
        
    Until #True
    
  Next
  
  ProcedureReturn result
  
EndProcedure

; ***************************************************************************************


;- Test

DisableExplicit

CompilerIf #PB_Compiler_IsMainFile
  
  OpenConsole()
  
  Define db, sql.s, result, card.s , index, value.s, 
  NewList rows.s()
  
  path.s = GetPathPart(ProgramFilename())
  
  db = RedInitDatabase(0, path, path, "TestDB.sqlite", 1)
  
  sql = "CREATE TABLE IF NOT EXISTS card (card_id VARCHAR(80) PRIMARY KEY, value REAL, date DATETIME);"
  result = RedDatabaseUpdate(0, sql, error)
  PrintN(RedDatabaseError())
  
  result = RedDatabaseSync(0, "card", "card_id", "date")
  Debug result
  If result < 0
    PrintN(RedDatabaseError())
  EndIf

  For index = 1000 To 1020
    card = "CARD-" + Str(index)
    sql = "insert into card (card_id, value, date) values ('" + card + "', 0.0, datetime('now'));"
    result = RedDatabaseUpdate(0, sql, error)
    ;PrintN(error\text)
    PrintN(RedDatabaseError())
  Next

  For index = 1000 To 1020
    card = "CARD-" + Str(index)
    value = StrF(Random(5000, 100))
    sql = "update card set value = " + value + ", date = datetime('now') where card_id = '" + card + "';"
    result = RedDatabaseUpdate(0, sql)
    PrintN(RedDatabaseError())
  Next
  
  sql = "select * from card;"
  result = RedDatabaseQuery(0, sql, rows())
  If result < 0
    PrintN(RedDatabaseError())
  EndIf
  ForEach rows()
    PrintN(rows())
  Next
  
  RedCloseDatabase(0)
  
  Input()
  
CompilerEndIf
Bitte ausgiebig testen

FF :wink:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Redundante SQLite Datenbank

Beitrag von mk-soft »

Update v1.03

Neue Funktionen
- RedDatabaseError()
- RedDatabaseSync(...)

Entfällt
- Parameter: *errorstring

FF :wink:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: Redundante SQLite Datenbank

Beitrag von ts-soft »

Sag mal, wozu ist der Aufwand jetzt genau gut? Reicht es nicht mit CopyFile() eine Kopie zu erstellen?
Zumal SQLite Datenbanken ja nur aus einer einzigen Datei bestehen.

Gruß
Thomas
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Redundante SQLite Datenbank

Beitrag von mk-soft »

@ts-soft

Wenn eine Datenbank komplett weg ist dann hast du recht. Werde mir das Syncronisieren der Datenbank noch mal genauer anschauen.
Wenn beide Datenbanken noch existieren muss man vieleicht nur noch den Zeitstempel überprüfen.

Die vorherige Version hat den Praxistest erfolgreich bestanden. War für eine Feuerwehrveranstaltung mit Partykarten mit Barcode und Laserscanner an den Kassen.

P.S.
Da wollte ich sicherstellen das, wenn das Laptop mit den Kassen-Server hobs geht, nicht die Datenbank mit den Credits verloren geht.
Somit wurde die sekundäre Datenbank mit einen anderen Laptop verbunden.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
kunstlust
Beiträge: 259
Registriert: 12.04.2012 23:47
Wohnort: Hannover
Kontaktdaten:

Re: Redundante SQLite Datenbank

Beitrag von kunstlust »

Antworten