Ich arbeite jetzt schon lange mit mod_harbour. Es gibt hier eine verzögerte Serveranforderung von durchschnittlich 150 ms beim Starten. Dann läuft alles sehr schnell. Ich habe nun interessehalber Tests mit einem einfachen "Hello World" mit PHP und mod_harbour durchgeführt. PHP ist sehr schnell.
Bei großen Programmen fällt das nicht wirklich auf. Aber wenn man schnell ein kleines Programm öffnet und warten muss, ist das ärgerlich. In diesem Fall ist die Toleranz gering.
Ich habe deshalb die Fetch-Anfrage auf einen PHP-Endpoint programmiert. Da ich ja mit DBF arbeite, versuchte ich es mit einer selbstgeschriebenen Low-Level-Funktion. Die Performance in Datenbankgrößen, mit denen ich arbeite, funktioniert gut. Hier ein Link:
https://www.modharbour.club/workschedule/kundentbl.html
Dateigröße: 200000 Datensätze
Inzwischen habe ich die PHP-Funktionen auch nach harbour übersetzt, und dort funktioniert es ebenfalls gut.
Code: Alles auswählen
//prg
#include "FiveWin.ch"
static nStartTime, nDuration
PROCEDURE Main
LOCAL cFilePath := "c:\fwh\samples\Data\database.dbf"
LOCAL cName := "clark"
LOCAL aResult
aResult := FindNameInDbf(cFilePath, cName)
RETURN
FUNCTION FindNameInDbf(cFilePath, cName)
LOCAL nHandle := FOPEN(cFilePath)
LOCAL cHeader := SPACE(32)
LOCAL nHeaderSize, nRecordSize, nNumRecords
LOCAL aFieldDescriptors := {}
LOCAL aFieldOffsets := {}
LOCAL nOffset := 0
LOCAL cFieldDescriptor, cFieldName
LOCAL nFieldLength
LOCAL nNameOffset, nNameLength
LOCAL aMatchingRecords := {}
LOCAL cRecord, cExtractedName
LOCAL hField, hRecordData
LOCAL i, j
LOCAL cFieldValue
LOCAL hFieldDescriptor := { => }
LOCAL nFound := 0
LOCAL cFileData
LOCAL nVersion
LOCAL nYear
LOCAL nMonth
LOCAL nDay
LOCAL LastUpdate
Msginfo("Start Suche")
nStartTime := SECONDS()
IF nHandle == -1
? "Konnte die Datei nicht öffnen."
RETURN {}
ENDIF
// Read entire file into memory
cFileData := MEMOREAD(cFilePath)
// Header lesen
cHeader := LEFT(cFileData, 32)
nVersion := ASC(LEFT(cHeader, 1))
// Display human-readable format based on the version number
DO CASE
CASE nVersion == 0x02
nVersion := "Version: FoxBASE"
CASE nVersion == 0x03
nVersion := "Version: dBASE III PLUS (no memo)"
CASE nVersion == 0x30
nVersion := "Version: Visual FoxPro"
CASE nVersion == 0x31
nVersion := "Version: Visual FoxPro (with AutoIncrement field)"
CASE nVersion == 0x43
nVersion := "Version: dBASE IV SQL table files, no memo"
CASE nVersion == 0x63
nVersion := "Version: dBASE IV SQL system files, no memo"
CASE nVersion == 0x83
nVersion := "Version: FoxBASE+/dBASE III PLUS, with memo"
CASE nVersion == 0x8B
nVersion := "Version: dBASE IV with memo"
CASE nVersion == 0xCB
nVersion := "Version: dBASE IV SQL table files, with memo"
CASE nVersion == 0xF5
nVersion := "Version: FoxPro 2.x (or earlier) with memo"
CASE nVersion == 0xE5
nVersion := "Version: HiPer-Six format with SMT memo files"
CASE nVersion == 0xFB
nVersion := "Version: FoxBASE"
OTHERWISE
nVersion := "Unknown DBF version"
ENDCASE
nYear := ASC(SUBSTR(cHeader, 2, 1)) + 1900 // Year (YY)
nMonth := ASC(SUBSTR(cHeader, 3, 1)) // Month (MM)
nDay := ASC(SUBSTR(cHeader, 4, 1)) // Day (DD)
// ? "Last Update Date:", STR(nYear, 4) + "-" + TRANSFORM(nMonth, "@Z 99") + "-" + TRANSFORM(nDay, "@Z 99") // Display the date in YYYY-MM-DD format
LastUpdate := STR(nYear, 4) + "-" + TRANSFORM(nMonth, "@Z 99") + "-" + TRANSFORM(nDay, "@Z 99")
// Byte-Interpretation der Header-Daten
nNumRecords := (ASC(SUBSTR(cHeader, 5, 1)) + (ASC(SUBSTR(cHeader, 6, 1)) * 256) + (ASC(SUBSTR(cHeader, 7, 1)) * 65536) + (ASC(SUBSTR(cHeader, 8, 1)) * 16777216))
nHeaderSize := (ASC(SUBSTR(cHeader, 9, 1)) + (ASC(SUBSTR(cHeader, 10, 1)) * 256))
nRecordSize := (ASC(SUBSTR(cHeader, 11, 1)) + (ASC(SUBSTR(cHeader, 12, 1)) * 256))
// Felddeskriptoren lesen
FOR i := 33 TO nHeaderSize STEP 32
cFieldDescriptor := SUBSTR(cFileData, i, 32)
IF ASC(LEFT(cFieldDescriptor, 1)) == 13
EXIT
ENDIF
cFieldName := RTRIM(SUBSTR(cFieldDescriptor, 1, 11))
nFieldLength := ASC(SUBSTR(cFieldDescriptor, 17, 1))
AADD(aFieldDescriptors, { "name" => cFieldName, "length" => nFieldLength })
NEXT
// Feld-Offsets berechnen
FOR i := 1 TO LEN(aFieldDescriptors)
hFieldDescriptor := aFieldDescriptors[i]
AADD(aFieldOffsets, { hFieldDescriptor["name"], nOffset, hFieldDescriptor["length"] })
nOffset += hFieldDescriptor["length"]
NEXT
nNameOffset := AScan(aFieldOffsets, { |a| LEFT(a[1], 10) = "LAST" })
nNameLength := aFieldOffsets[nNameOffset, 3]
// Process records
FOR i := 1 TO nNumRecords
cRecord := SUBSTR(cFileData, nHeaderSize + (i - 1) * nRecordSize + 1, nRecordSize)
cExtractedName := ALLTRIM(LOWER( SUBSTR(cRecord, aFieldOffsets[nNameOffset, 2] + 1, nNameLength) ))
// IF cExtractedName = cName
nFound += 1
hRecordData := { "recno" => i }
nOffset := 0
FOR j := 1 TO LEN(aFieldDescriptors)
hField := aFieldDescriptors[j]
cFieldValue := RTRIM(SUBSTR(cRecord, nOffset + 2, hField["length"]))
hRecordData[hField["name"]] := cFieldValue
nOffset += hField["length"]
NEXT
AADD(aMatchingRecords, hRecordData)
if nFound > 50
EXIT
ENDIF
// ENDIF
NEXT
nDuration := (SECONDS() - nStartTime) * 1000 // Dauer in Millisekunden
xbrowse(aMatchingRecords, "LastUpdate "+ LastUpdate + " NumRecords"+ str(nNumRecords) + " Searchtime: " + STR(nDuration, 10, 2) + " ms Records found: " + STR(nFound))
RETURN NIL
Code: Alles auswählen
#include 'fivewin.ch'
//----------------------------------------------------------------------------//
REQUEST DBFCDX
REQUEST DBFFPT
//----------------------------------------------------------------------------//
function Main()
local aFields := { { "STAFFCODE", "C", 20, 0 },;
{ "FNAME", "C", 20, 0 },;
{ "QUALIF", "C", 5, 0 },;
{ "EMAIL", "C", 45, 0 },;
{ "MATCHCODE", "C", 100, 0 } }
DbCreate( "staff.dbf", aFields, "DBFCDX" )
//DbCreate(".\data\dbf1.dbf",aFields )
? "Erstellt"
RETURN NIL
INIT PROCEDURE PrgInit
SET CENTURY ON
SET EPOCH TO YEAR(DATE())-98
SET DELETED ON
SET EXCLUSIVE OFF
REQUEST HB_Lang_DE
HB_LangSelect("DE")
SET DATE TO GERMAN
SetHandleCount(205)
rddsetdefault( "DBFCDX" )
SetGetColorFocus()
EXTERN DESCEND
SetBalloon( .F. )
SetKinetic( .T. )
fwNumFormat( 'E', .t. )
RETURN
//----------------------------------------------------------------------------//
Ich habe mich schon lange nicht mehr mit der internen Struktur des DBF-Formats beschäftigt.
Ich konnte mir kaum vorstellen, dass es so einfach aufgebaut ist. So viel unnötige Verpackung, die im Laufe der Zeit beigepackt wurde. Es ist klar, dass Datenzugriff über Netzwerk (Fileserver) mehr Anforderungen stellt, aber am Webserver, wo nur der Server auf die lokalen Daten am gleichen PC zugreift, funktioniert das gut.
Word, Excel, Access haben auch keine Dateisperren, sondern erstellen parallele Sperrdateien. Ich mache es genauso.
Vielleicht interessiert es jemanden.
LG
Otto
PS: Falls jemand am PHP-source interessiert ist:
https://www.modharbour.club/workschedul ... tabase.zip