MySQL Plugin in C - Fehler
-
Wutz schrieb:
- Abbruchzeile rausfinden
- strncpy raus, dafür strncat oder sprintf %.*s
- strtok raus, dafür strsep
- for statt while
- assert einbauen
- symbolische Konstanten für Arraygrößen verwendenfor(i=0; i<=max_index; i++) { for (j=0; j<=indexKeywords; j++)
möchte höchstwahrscheinlich
for(i=0; i<max_index; i++) { for (j=0; j<indexKeywords; j++)
heißen.
if ((max_index+1) <= (treffer_titel+treffer_titel_like)) {
sieht sehr fragil aus.
Danke erstmal für deine Antwort.
Das < Zeichen bei der Schleife passt schon - das ist der max. Index vom Array und nicht die Größe.Abbruchzeile rausfinden ist sowas. Wie gesagt - die Funktion funktioniert eigentlich richtig und gibt beim Ausführen auch die richtigen Werte zurück.
Der Fehler tritt nach dem 1000 Aufruf ca auf.
zB diese Testdaten:
Suchbegriff: msysql datenbank administrator
Titel des Inserats: datenbank administrator
Kewwords: mysql|sql|oracle|datenbank administrator|datenbankadministrator|db administrator|datenbank administration|datenbankadministration|plsql|db administration|datenbankverwaltungFunktioniert einwandfrei im Test. Beim x-Durchlauf sehe ich dann im sql-log (wenn ich mit printf die Daten mitschaue) bei der letzten Schleife (Keywords durchsuchen) dass er in den Variablen eigenartige Daten hat.
zB
Vergleich der Keywords mit dem Suchbegriff
mysql vs. mysql
mysql vs. sql
mysql vs. oracle
....
datenbank vs. mysql
...
datenbank vs. datenbankadministration
datenbank vs. datenbankadplsql (===> FEHLER: eigentlich plsql)Sowas passiert nach dem 1000 Aufruf ca. Auf einmal steht auf dem String noch der alte Wert und er überschreibt ihn. Aber nur wenn die DB unter starker Last steht...
Lg
-
Inzwischen kenn ich das Problem. Wenn zwei User gleichzeitig die Funktion aufrufen beginnt sich der Speicherbereich zu überschreiben und die Daten werden fehlerhaft.
Laut MySQL sollte man keinen lokalen Variablen verwenden. In der init-Funktion soll der benötigte Speicherbereich auf initd->ptr zugewiesen werden. Dieser erwartet sich (char
als Datentyp.
Leider sind meine C-Kenntnisse zu beschränkt damit ich weiß wie ich diese drei String-Arrays
stringArray a_suchbegriffe; a_suchbegriffe = MallocStringArray(250,30); stringArray a_keywords; a_keywords = MallocStringArray(250,50); stringArray a_keywords_phrasen; a_keywords_phrasen = MallocStringArray(250,50);
auf eine Ebene bringe und diese dann zu (char
umwandle damit ich sie in der Hauptfunktion mit Daten befüllen kann.
Kann mir jemand bitte helfen?
Danke LG
-
rappit schrieb:
Inzwischen kenn ich das Problem. Wenn zwei User gleichzeitig die Funktion aufrufen beginnt sich der Speicherbereich zu überschreiben und die Daten werden fehlerhaft.
Genau deswegen
Wutz schrieb:
- strtok raus, dafür strsep
weil strtok (in den allermeisten Implementierungen) prozessübergreifenden (statischen) Speicher verwendet.
strtok ist also per se nicht reentrant, es ist also völlig klar, dass dieses Ignorieren der Nichtreentranz irgendwann zu Fehlern führt.rappit schrieb:
Laut MySQL sollte man keinen lokalen Variablen verwenden.
Ja ja, ist klar. Die sehen sowas nicht gern. Ist aber IMHO in deinem Fall nicht vorrangig.
Gib mal die Definition von UDF_INIT/UDF_ARGS und ein paar charakteristische übergebene Testdaten an, ich schreibe dir dann mal auf, wie ich mir die Sache denke.
-
Wutz schrieb:
rappit schrieb:
Inzwischen kenn ich das Problem. Wenn zwei User gleichzeitig die Funktion aufrufen beginnt sich der Speicherbereich zu überschreiben und die Daten werden fehlerhaft.
Genau deswegen
Wutz schrieb:
- strtok raus, dafür strsep
weil strtok (in den allermeisten Implementierungen) prozessübergreifenden (statischen) Speicher verwendet.
strtok ist also per se nicht reentrant, es ist also völlig klar, dass dieses Ignorieren der Nichtreentranz irgendwann zu Fehlern führt.rappit schrieb:
Laut MySQL sollte man keinen lokalen Variablen verwenden.
Ja ja, ist klar. Die sehen sowas nicht gern. Ist aber IMHO in deinem Fall nicht vorrangig.
Gib mal die Definition von UDF_INIT/UDF_ARGS und ein paar charakteristische übergebene Testdaten an, ich schreibe dir dann mal auf, wie ich mir die Sache denke.
Danke mit der Begründung warum man strsep verwenden soll, machts jetzt Sinn
Vielen Dank!!
Zur Funktion
Aufruf über Mysql: SELECT *, relevance('C Entwickler', 'Software Developer', 'Software Entwickler|C Entwickler|Programmierer', 1) FROM inserat...
1. Parameter: Suchbegriff
2. Parameter: Inserattitel
3. Parameter: Keywords zum Inserat
4. Parameter: Max. Levenshtein DistanzDer gesamte Programmcode steht schon im Thread-Beginn.
Hier mal die Aufrufübersicht
void relevance_deinit(UDF_INIT *initid) { if (initid->ptr != NULL) { free(initid->ptr); } } my_bool relevance_init(UDF_INIT *initid, UDF_ARGS *args, char *message) { if ((args->arg_count != 4) || (args->arg_type[0] != STRING_RESULT || args->arg_type[1] != STRING_RESULT || args->arg_type[2] != STRING_RESULT || args->arg_type[3] != INT_RESULT)) { strcpy(message, "Function requires 4 arguments, (string, string, string, int)"); // Hier sollte der Speicher für initd->ptr reserviert werden den die Hauptfunktion dann weiter unten verwendet. Laut der MySQL Doku sollten die ganzen String-Arrays die bei mir in der Hauptfunktion stehen hier auf diesem Pointer reserviert werden return 1; } initid->maybe_null = 0; //doesn't return null return 0; } longlong relevance(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error) { .... }
Aufrufsquenz MySQL:
init();
Hauptfunktion()
deinit();Brauchst du sonst noch Details die du nicht dem Quellcode am Anfang entnehmen kannst?
Danke, LG
-
Ich habe nicht verstanden, was die Funktion überhaupt zurückliefern soll.
Momentan liefert sie doch gar nicht irgendwelche lev.algo. Werte zurück, sondern nur 0,1, oder irgendwelche Fehlercodes.
Und was soll bei strlen<=3 rückgegeben werden?
Du zählst zwar die Treffer, aber was passiert denn anschließend damit?
Ich könnte mir vorstellen, dass der min/max lev.algo.-Wert über alle Treffer ermittelt und dann rückgegeben werden soll oder zumindest die Anzahl der Treffer?!
"treffer_titel_alt" habe ich auch nicht verstanden.Auf jeden Fall lässt sich der Code auf 20-10% kürzen und dafür aber seeeehr deutlich beschleunigen.
Welchen Compiler verwendest du denn und welche Plattform?
-
Mittlerweile habe ich eine ungefähre Vorstellung, was du erreichen willst (an deinem Code leider nicht einfach abzulesen), hier mal mein Vorschlag:
longlong abweichung(UDF_INIT *initid,char *suche,char *in,const char *e,int distance,char *is_null,char *error) { #define MINDESTLAENGE 4 longlong r=LONG_MAX; /* "schlechteste" Übereinstimmung annehmen */ while( in!=e ) /* im zu durchsuchenden char-Array alle "Worte" durchsuchen, also z.B. "abcd" in "xyz\0qwer\0usw\0" */ { assert( (printf("%s ? %s\n",suche,in),1) ); if( strlen(suche)>=MINDESTLAENGE && strlen(in)>=MINDESTLAENGE ) /* nur ab MINDESTLAENGE Zeichen die Suche durchführen */ { if( !strcoll(in,suche) ) return 0; /* falls identisch (inkl. locale), dann "beste" Übereinstimmung liefern + Ende */ if( strstr(in,suche) ) return 0; /* falls enthalten, dann "beste" Übereinstimmung liefern + Ende */ { longlong tmp=levenshtein_k_base(initid,suche,in,distance,is_null,error); assert( tmp>=0 ); /* Code geht davon aus, dass keine neg. Werte geliefert werden */ if( 0==tmp ) return 0; /* falls <lv> "beste" Übereinstimmung liefert => Ende */ if( tmp<r ) r=tmp; /* sonst den besten Wert merken */ } } in+=strlen(in)+1; } return r; } longlong relevance(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error) { longlong r=LONG_MAX; /* "schlechteste" Übereinstimmung annehmen */ /* Such-Array bilden z.B. "C Entwickler" => "c\0entwickler\0centwickler\0" */ char *s0=alloca(2+2*(args->lengths[0])),*c=s0?s0:(abort(),NULL),*e=c; memcpy(c,args->args[0],args->lengths[0]);c[args->lengths[0]]=0; while(*e) *e=tolower((unsigned char)*e),e++; strcpy(++e,s0); s0[strlen(s0)]=' '; while(*e) {if(*e==' ') memmove(e,e+1,strlen(e)); ++e;} ++e; c=s0; while(*c) {if(*c==' ') *c=0; ++c;} /***************************************************************************/ /* Inserattitel-Wörter durchsuchen und Ende, falls "beste" Übereinstimmung hier schon gefunden */ c=s0; { char *s1=alloca(1+args->lengths[1]),*x=s1?s1:(abort(),NULL); memcpy(x,args->args[1],args->lengths[1]);x[args->lengths[1]]=0; while(*x) *x=tolower((unsigned char)*x),x++; x=s1; while(*x) {if(*x==' ') *x=0; ++x;} ++x; while( c!=e ) /* alle Such-Wörter durchlaufen */ { longlong tmp=abweichung(initid,c,s1,x,*(int *) args->args[3],is_null,error); if( 0==tmp ) return 0; if( tmp<r ) r=tmp; c+=strlen(c)+1; } } /* Inserattext-Wörter durchsuchen und Ende, falls "beste" Übereinstimmung hier gefunden */ c=s0; { char *s1=alloca(1+args->lengths[2]),*x=s1?s1:(abort(),NULL); memcpy(x,args->args[2],args->lengths[2]);x[args->lengths[2]]=0; while(*x) *x=tolower((unsigned char)*x),x++; x=s1; while(*x) {if(*x==' '||*x=='|') *x=0; ++x;} ++x; while( c!=e ) { longlong tmp=abweichung(initid,c,s1,x,*(int *) args->args[3],is_null,error); if( 0==tmp ) return 0; if( tmp<r ) r=tmp; c+=strlen(c)+1; } } return r; /* "besten" d.h. minimalen Wert vom lev.algo. rückliefern */ }
Der Code ist bis auf alloca strikt C89 (läuft also auf jedem C89-Compiler aufwärts).
Noch zu tun:
- locale-Berücksichtigung instrstr
undlevenshtein_k_base
- auch andere Whitespaces außer ' ' können vorkommen
- die Aufnahme von vielfältigen Freitexten in ein SQL-Statement ist gelinde gesagt suboptimal (von jedem Datenbankspezi kriegst du dafür auf die Finger), weil Statement-Cache und Hashtable deiner DB unnötig strapaziert werden und damit nicht nur deine unmittelbar in diesem Zusammenhang stehenden Statements verlangsamt werden, sondern auch alle anderen in der aktuellen DB-Session.
Das führt mit der Zeit dazu, dass viele deiner (Webseiten)Dateninhalte im Statement-Cache rumliegen, wo sie definitiv nicht hingehören.
Sinnvollerweise setzt man hier Parameter/Bindvariablen ein, also
stattSELECT *, relevance('C Entwickler', 'Software Developer', 'Software Entwickler|C Entwickler|Programmierer', 1) SELECT *, relevance('kdfhgkdfhg', 'ksjad jksdjkf', 'jsadgh jsdgh üqop ksdjf', 1) ... viele weitere SELECT *, relevance('pqownfdkg', 'opqw oiqiw', 'qerew podgfo', 1)
viel besser
SELECT *, relevance(:1,:2,:3,:4)
Sowas sollte selbst mysql beherrschen, siehe dazu die API.
-
Wutz schrieb:
Ich habe nicht verstanden, was die Funktion überhaupt zurückliefern soll.
Momentan liefert sie doch gar nicht irgendwelche lev.algo. Werte zurück, sondern nur 0,1, oder irgendwelche Fehlercodes.
Und was soll bei strlen<=3 rückgegeben werden?
Du zählst zwar die Treffer, aber was passiert denn anschließend damit?
Ich könnte mir vorstellen, dass der min/max lev.algo.-Wert über alle Treffer ermittelt und dann rückgegeben werden soll oder zumindest die Anzahl der Treffer?!
"treffer_titel_alt" habe ich auch nicht verstanden.Auf jeden Fall lässt sich der Code auf 20-10% kürzen und dafür aber seeeehr deutlich beschleunigen.
Welchen Compiler verwendest du denn und welche Plattform?Das sind keine Fehlercodes. Das ist die Relevanz. 100 Punkte Relevanz bedeutet der Suchbegriff ist genau im Inseratstitel vorhanden. 75 bedeutet der Suchbegriff steht als Phrase in den Keywords und 50 bedeutet die Suchbegriffe stehen irgendwo in den Keywords.
Zum strlen < 3: Normalerweise wird der Wortvergleich mit Levenshtein gelöst um Tippfehler vom User auszuschließen. Bei Wörtern mit einer Länge von <= 3 wird ein direkter Vergleich (Stringvergleich) durchgeführt da sonst falsche Wörter gefunden werden. Bei "IT" sollte man beispielsweise keinen Fehler machen sonst bedeut das Wort ja was anderes. Bei längern Wörtern ist die Chance auf einen Tipp/Rechtschreibfehler allerdings höher (zB "Techniker" vs. "Technicker").
Kompiliert wird das mit GCC (Linux):
gcc -o relevance.so -shared relevance.c -I /usr/include/mysql/Das Programm ist standalone leider bei mir auch nicht lauffähig sondern nur wenn ich es über MySQL ausführe. Das liegt aber wohl auch an meinen beschränkten C-Kenntnissen.
Zu deinem Code-Vorschlag: Vielen Dank ich werd mir das jetzt mal ganz genau durchschauen - ich hoffe es kommen nicht zuviel Fragen auf!
-
Hallo Wutz,
danke nochmal für deine Mühe - der Code klappt wunderbar. Musste die Logik allerdings umbauen da die Berechnung anders gedacht war. Hier mal meine Version davon. Wenn du Zeit hast ich hab noch ein paar Fragen in den Code geschrieben da ich gewisse Dinge von dir nicht verstehe.
Mein größtes Problem ist dass ich die Anzahl der Suchbegriffe nicht zählen kann (Zeile 197).
Meine Idee wäre mit "strpbrk" oder ähnlichen einfach die Anzahl der Leerzeichen (+1) zu zählen. Gibts ne bessere Alternative?Lg rappit
Edit: Sorry für die dumme Frage mit der Anzahl, bin inzwischen schon draufgekommen:
Zeile 176 einfach sowhile(*c) {if(*c==' '){ *c=0; anz_suchbegriffe++; } ++c; }
Den restlichen oberen Teil versteh ich trotzdem nicht
Das einzige, letzte logische Problem ist das in Zeile 226.
-
Du hast das Prinzip meines Codevorschlages nicht verstanden.
Stringlisten werden hier nicht wie häufig einzeln angesprochen sondern nur innerhalb eines Streams, d.h. du brauchst überhaupt keine 'anzahlderstrings', das macht alles die while-Schleife.
Die Fragen lese ich mir bei Gelegenheit mal näher durch, trotzdem war dein bisheriger Code hinsichtlich des <lv> ignorant, d.h. die mühsam aufgerufene <lv> Funktion liefert einen mühsam ermittelten Wert für ein Wichtung der Übereinstimmung, den du aber komplett ignorierst, da du ihn nicht an dein SQL durchreichst.
-
Wutz schrieb:
Du hast das Prinzip meines Codevorschlages nicht verstanden.
Stringlisten werden hier nicht wie häufig einzeln angesprochen sondern nur innerhalb eines Streams, d.h. du brauchst überhaupt keine 'anzahlderstrings', das macht alles die while-Schleife.
Die Fragen lese ich mir bei Gelegenheit mal näher durch, trotzdem war dein bisheriger Code hinsichtlich des <lv> ignorant, d.h. die mühsam aufgerufene <lv> Funktion liefert einen mühsam ermittelten Wert für ein Wichtung der Übereinstimmung, den du aber komplett ignorierst, da du ihn nicht an dein SQL durchreichst.Verstehe, danke
Dennoch hast du damit mein Speicherproblem gelöst und ich bekomme keine Fehler mehr. Vielen Dank!