PostgreSQL Externe C-Funktion



  • Hallo ich habe hier eine unausgereifte und externe C-Funtion, die bestimmt voller Fehler ist. Erstmal zur Funktion selbst. Sie nimmt sich einen Koordinatenstring aus einer Zeile , splittet ihn zweimal mit dem Delimiter ' ' und ',' damit ich an die einzelnen Punkte rankomme. Danach wandelt es das Ergebnis in einen Double um und setzt alle Einzelpunkte ^2 mit der pow-funktion. Danach summiert es das Ergebnis und setzt es in eine Wurzel und gibt das Ergebnis dann aus. Die PL/pgSQL-Funktion wunderbar. Jetzt muss ich nur noch die C-Funktion dafür zum laufen bringen. Hoffe ihr könnt mir dabei helfen.

    Zu mir selbst: 1 1/2 Monate Programmiererfahrung also noch recht jung dabei 🙂

    PG_FUNCTION_INFO_V1(FCT_Norm);
    Datum
    FCT_NORM(PG_FUNCTION_ARGS)
    {
    
    FuncCallContext     *funcctx;
    int                  call_cntr;
    int                  max_calls;
    char       *str = PG_GETARG_CSTRING(0);
    
    char        *delim1=" ";
    char        *delim2= ",";
    char        **firstone;
    char        **secondone ;
    double x = 0;
    double y = 0;
    if (SRF_IS_FIRSTCALL())
    
            {
            MemoryContext   oldcontext;
    
            //create a function context for cross-call persistence 
            funcctx = SRF_FIRSTCALL_INIT();
    
            // switch to memory context appropriate for multiple function calls 
            oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
    
            /* total number of tuples to be returned */
            funcctx->max_calls = PG_GETARG_UINT32(0);
            MemoryContextSwitchTo(oldcontext);
    
            }   
    
                call_cntr = funcctx->call_cntr;
                max_calls = funcctx->max_calls;
                funcctx = SRF_PERCALL_SETUP();
    
                while(call_cntr < max_calls)
                {
    
                double n,i,sum,ergebnis;
    
                    //Split the string
                    firstone=split_string1(str,delim1);
                    secondone=split_string1(*firstone,delim2);
    
                    //Convert to double
                    double x = atofn(*secondone,100);    //here he wants a ;
    
                    //Sum it up and pow
                    for (i = 0; i <= max_calls; ++i)
                        {
                          sum += pow(x, 2);
    
                          //SQRT
                          ergebnis = sqrt(sum);
    
                          SRF_RETURN_NEXT(funcctx,ergebnis);
                        }
                if(call_cntr = max_calls){
                    SRF_RETURN_DONE(funcctx);
                }
            }
    
    }
    //------------------------------------------------------------------------------
    
    //Double-Converter
    double atofn (char *src, int n) {
      char tmp[100]; // big enough to fit any double
    
      strncpy (tmp, src, n);
      tmp[n] = 0;
      return atof(tmp);
    }
     //------------------------------------------------------------------------------
     // The Splitfunction
            char** split_string1(char* str, const char* delim)
            {
            char* tmp;
    
              char** t = (char**)malloc(sizeof(char*) * 1024);
              char** tokens = t;
    
                 tmp = strtok(str, delim);
    
                 while(tmp != NULL)
                     {
                      *tokens = (char*)malloc(sizeof(char) * strlen(tmp+1));
                       *tokens = strdup(tmp);
                              tokens++;
                          tmp = strtok(NULL, delim);
                      }
    
                        return t;
                    }
    

    Diese Funktion soll das Gegenstück zu dieser Funktion werden :

    CREATE OR REPLACE FUNCTION public.split_string10(_str text )
    RETURNS SETOF float8 AS
    
    $func$
    DECLARE 
    _delim1 text = ' ';
    _delim2 text = ',';
    
    BEGIN
    
       RETURN QUERY
       WITH test AS (
       SELECT pow(unnest(string_to_array(a, _delim2))::float8,2) as b
    		-- or do something else with the derived table from step 1
    	FROM   unnest(string_to_array(_str, _delim1)) as a)
       SELECT sqrt(sum(b)) FROM test;
    
    END
    $func$ LANGUAGE plpgsql IMMUTABLE;
    

    Aufgerufen wird sie durch:

    select split_string10(trim(')' from trim('LINESTRING(' from st_AsText (coordinates)))) from germany_.ttopology ;
    


  • Wo sind die free ?
    Du brauchst für jedes malloc und strdup auch ein free .

    Aber warum nimmst du nicht einfach scanf oder strchr und strtod ohne diese merkwürdige hin und her kopieren.

    Statt sum += pow(x, 2); nimm mal sum += x * x;



  • Ich muss das Teilergebnis, also nach der splittung, in eine euklidische norm setzten (also (x1)2+(x2)2+....(xn)^2 und das alles in die eine Wurzel setzten). Da würde doch x+x falsch sein oder?.
    Wo muss denn das free dann hin ?



  • sataide schrieb:

    Ich muss das Teilergebnis, also nach der splittung, in eine euklidische norm setzten (also (x1)2+(x2)2+....(xn)^2 und das alles in die eine Wurzel setzten). Da würde doch x+x falsch sein oder?.

    Da steht xx* was soviel ist wie x2

    xx* wird schneller berechnet als pow(x,2) (wenn der Compiler nicht optimiert)

    sataide schrieb:

    Wo muss denn das free dann hin ?

    frühestens, wenn du den Zeiger nicht mehr brauchst, spätestens bevor du ihn mit einem neuen Wert beschreibst.

    *tokens = (char*)malloc(sizeof(char) * strlen(tmp+1));
    
    // da in der nächsten Zeile *tokens überschrieben wird, muss hier ein free hin. aber dann ist auch das malloc sinnfrei
                       *tokens = strdup(tmp);
    

    Du rufst in deiner while -Schleife zweimal split_string1 auf.
    Und bei jedem Aufruf holst du Speicher für 1024 char -Pointer und noch für zwei Strings. ( strdup holt auch Speicher per malloc )

    strtok verändert den Ursprungsstring.

    Wann wird eigentlich str (in der while-Schleife) neu gesetzt?

    Wie sieht so ein String aus, den du mit PG_GETARG_CSTRING(0); bekommst?



  • Warum willst du eine bestehende (und offenbar wohl funktionierende) SQL-Funktion ersetzen? Wieviele Nanosekunden willst du vielleicht sparen?
    Wie du schon vermutet hast, ist das alles Müll, was du da machst.
    Obendrein erscheint mir die Doppelsplittung (nach " " und dann nach ",") zweifelhaft, warum nimmst du nicht einfach sofort eine Splittung nach "," vor?
    Das Ganze geht auch einfacher mit sscanf, da brauchst du weder malloc/free noch strtok und kannst mit konstanten Strings arbeiten.



  • Erstmal Danke für die Interesse und die Hilfe 🙂

    @Dirk
    Also fangen wir mal von vorne an. Dass das Müll ist, ist klar 😃 . Ich mach nur während des Praxissemesters einen Test, ob die externe C-Funktion schneller ist als eine PL/pgSQL- Funktion.

    Also so eine LineString sieht folgendermaßen aus:

    '3.584731 60.739211,3.590472 60.738030,3.592740 60.736220'

    Das Trim in der Aufruffunktion im PL/pgSQL ist deshalb da weil der String eigentlich so aussieht:

    LINESTRING('3.584731 60.739211,3.590472 60.738030,3.592740 60.736220')

    Wie du siehst muss ich ihn 2 mal splitten um an die Einzelpunkte zu kommen, damit ich konvertieren und berechnen kann. Einmal auf das Leerzeichen(' ') und einmal auf das Komma(',').

    Ich hol mir ja die Strings aus einer Datenbank. Diese Spalte hat ca. 6 1/2 Millionen Strings bzw. Zeilen, die diese Strings enthalten. Meine Funktion soll halt durch jede Zeile durchgehen, Splitten, in ein Double konvertieren, und berechnen.
    Und am Ende natürlich ausgeben und in die nächste Zeile springen und das selbe machen bis die Spalte durch ist.

    @Wutz
    Ich will sie ja nicht ersetzten , ich will die beiden miteinander vergleichen.
    Ja die PL/pgSQL-Funktion funktioniert wunderbar und braucht für den gesamten Ablauf ca. 8 Minuten.
    Davon 6 1/2 für die Funktion und 1 1/2 für den Leseprozess.
    Jede einzelne Nanosekunde wäre ein Erfolg 🙂



  • sataide schrieb:

    Also so eine LineString sieht folgendermaßen aus:

    '3.584731 60.739211,3.590472 60.738030,3.592740 60.736220'
    Das Trim in der Aufruffunktion im PL/pgSQL ist deshalb da weil der String eigentlich so aussieht:
    LINESTRING('3.584731 60.739211,3.590472 60.738030,3.592740 60.736220')

    Und welche Werte willst du davon haben?
    jeden (1,2,3,4,5,6)?
    jeden ungeraden (1,3,5) ?
    jeden geraden (2,4,6) ?
    hat jede Zeile 3 Wertepaare?



  • 1.Ich will jeden Wert davon haben weil ja eine Berechnung durchführen muss.
    2.Nein, die Strings sind verschieden lang aber meistens haben sie zwischen 2-5 Werte Paaren.



  • Den gesamten String in C verarbeiten:

    int main(void)
    {
      const char *s="LINESTRING('3.584731 60.739211,3.590472 60.738030,3.592740 60.736220')";
      int n=0;
      double d;
      s=strchr(s,'\'')+1;
      while( 1==sscanf(s+=n,"%lf%*1[ ,]%n",&d,&n) )
        printf("\n%f",d);
      return 0;
    }
    

    http://ideone.com/f9U1KJ



  • Ich würde die 1 beim %*1[ ,] noch weglassen.
    Das macht es etwas flexibler bezüglich der Anzahl der Trennzeichen



  • Wutz schrieb:

    Den gesamten String in C verarbeiten:

    int main(void)
    {
      const char *s="LINESTRING('3.584731 60.739211,3.590472 60.738030,3.592740 60.736220')";
      int n=0;
      double d;
      s=strchr(s,'\'')+1;
      while( 1==sscanf(s+=n,"%lf%*1[ ,]%n",&d,&n) )
        printf("\n%f",d);
      return 0;
    }
    

    http://ideone.com/f9U1KJ

    Wow vielen Dank. Damit komm ich meinem Ziel schon bedeutend näher. Wenn du mir noch zeigen würdest wie ich mit dem Ergebnis weiterarbeiten kann wäre ich echt sehr dankbar. Bzw könnte ich in der WHile-Schleife die ausgegeben Werte summieren und quadrieren ?



  • Erweitere Zeile 9 nach deinem belieben, auch als Block. In d ist der aktuelle Wert gespeichert.



  • Packe den ganzen Kram in eine Funktion, übergib den const char* und arbeite innerhalb von while die Werte ab und überlege dir, was die Funktion evtl. rückliefern soll(Final-Ergebnis...).
    Sowas nennt man auch Programmdesign.



  • Sorry wenn ich doof frage aber warum const char?
    Nach meiner Denkweise ist es in jeder Zeile ein anderer String und somit doch kein const char. Aber belehrt mich eines besseren, bin auch nur ein Neuling und bereit was zu lernen 🙂

    @Wutz
    Meinste so:

    double return_of_spl_string(const char*s)
    {
      int n=0;
      double d;
      s=strchr(s,'\'')+1;
      while( 1==sscanf(s+=n,"%lf%*1[ ,]%n",&d,&n) )
    //...hier einfach pow,sum,sqrt anwenden ...
      return d;
    }
    


  • sataide schrieb:

    Sorry wenn ich doof frage aber warum const char?
    Nach meiner Denkweise ist es in jeder Zeile ein anderer String und somit doch kein const char. Aber belehrt mich eines besseren, bin auch nur ein Neuling und bereit was zu lernen 🙂

    const char*
    Du veränderst den String ja nicht in der Funktion. In der bleibt er const.

    sataide schrieb:

    Meinste so:

    double return_of_spl_string(const char*s)
    {
      int n=0;
      double d;
      s=strchr(s,'\'')+1;
      while( 1==sscanf(s+=n,"%lf%*1[ ,]%n",&d,&n) )
    //...hier einfach pow,sum,sqrt anwenden ...
      return d;
    }
    

    Das wird etwas schwieriger, da du ja jeden einzelnen Wert abspeichern willst.
    Aus der Funktion bekommst du aber nur das Ergebnis der ganzen Zeile.
    Oder du musst noch die aktuelle Summe mit übergeben und die neue wieder rausgeben.



  • const char* heißt, dass der übergebene String innerhalb der C Funktion nicht verändert werden darf, aber natürlich beim nächsten Funktionsaufruf durchaus ein anderer String sein darf. const-Design gehört auch zum Programmdesign und zwingt dazu, sich über die ausgetauschten Daten im Klaren zu sein.



  • @Dirk
    Meinste so?

    double return_of_spl_string(const char*s)
    {
      int n=0;
      double d;
      double sum;
      s=strchr(s,'\'')+1;
      while(sscanf(s+=n,"%lf%*1[ ,]%n",&d,&n) ) 
      {
    	  sum += pow(d, 2);  
      }
    
      return pow(sum, 0.5);
    }
    

    Diese Funktion sollte mir ein quadrierten und in eine Wurzel gepackten Wert zurückgeben.



  • DirkB schrieb:

    Ich würde die 1 beim %*1[ ,] noch weglassen.
    Das macht es etwas flexibler bezüglich der Anzahl der Trennzeichen

    Ich nicht. Die 1 wegzulassen hieße, dass aufeinanderfolgende "," nicht als Fehler erkannt würden, was etwa bei "11 22,33 44,,55 66" u.ä. wohl kaum wünschenswert sein dürfte. Aufeinanderfolgende Leerzeichen werden auch mit 1 schon berücksichtigt und führen nicht zum Abbruch. "Flexibel" ist immer subjektiv, will man bei Vorschriftenverstoß irgendwie weitermachen (um am Ende ein zweifelhaftes Ergebnis zu erhalten) oder will man abbrechen und über Vorschriftenverstoß informieren.



  • Nach meiner letzten Veränderung sieht der Code jetzt so aus.

    //------------------------------------------------------------------------------
    
    double return_of_spl_string(const char*s)
    {
      int n=0;
      double d;
      double sum;
      s=strchr(s,'\'')+1;
      while( 1==sscanf(s+=n,"%lf%*1[ ,]%n",&d,&n) ) 
      {
    	  sum += pow(d, 2);  
      }
      return pow(sum, 0.5);
    }
    
    //------------------------------------------------------------------------------
    PG_FUNCTION_INFO_V1(FCT_Norm);
    PGDLLEXPORT 
    Datum
    FCT_Norm(PG_FUNCTION_ARGS)
    	{
    
        FuncCallContext     *funcctx;
        int                  call_cntr;
        int                  max_calls;
        char       *_str = PG_GETARG_CSTRING(0);
    
    	if (SRF_IS_FIRSTCALL())
    
    			{
    			MemoryContext   oldcontext;
    
    			//create a function context for cross-call persistence 
    			funcctx = SRF_FIRSTCALL_INIT();
    
    			// switch to memory context appropriate for multiple function calls 
    			oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
    
    			/* total number of tuples to be returned */
    			funcctx->max_calls = PG_GETARG_UINT32(0);
    			MemoryContextSwitchTo(oldcontext);
    
    			}	
    
    				call_cntr = funcctx->call_cntr;
    				max_calls = funcctx->max_calls;
    				funcctx = SRF_PERCALL_SETUP();
    
    				while(call_cntr < max_calls)
    				{
    
    				double ergebnis;
    				ergebnis = return_of_spl_string(_str);
    
    				SRF_RETURN_NEXT(funcctx,ergebnis);
    				}
    				//When he finish the last row the function is done
    					if(call_cntr = max_calls)
    						{
    						SRF_RETURN_DONE(funcctx);
    						}
    }
    

    Er baut auch brav, die Funktion lässt sich auch mit CREATE ... einfügen bloß scheitert es an der Abfrage der Daten.

    Als Fehlermeldung kriege ich dann

    FEHLER:  Funktion mit Mengenergebnis in einem Zusammenhang aufgerufen, der keine Mengenergebnisse verarbeiten kann
    
    ********** Fehler **********
    
    FEHLER: Funktion mit Mengenergebnis in einem Zusammenhang aufgerufen, der keine Mengenergebnisse verarbeiten kann
    SQL Status:0A000
    


  • Diesen ganzen Kram mit dem Kontext und so brauchst Du doch nicht, oder?
    Ich meine Du bekommst einen LINESTRING und gibst da die Norm zurück.

    Würde dementsprechend nicht reichen:

    PG_FUNCTION_INFO_V1(FCT_Norm);
    PGDLLEXPORT
    Datum
    FCT_Norm(PG_FUNCTION_ARGS)
    {
      // ©Wutz (https://www.c-plusplus.net/forum/329834)
      int n=0;
      double d;
      double sum=0.0;
      const char *s=strchr(PG_GETARG_CSTRING(0),'\'')+1;
      while( 1==sscanf(s+=n,"%lf%*1[ ,]%n",&d,&n) )
      {
        sum += d*d;
      }
      PG_RETURN_FLOAT8(sqrt(sum)); 
    }
    


  • Solange er mir durch die Spalte geht ist mir das recht egal 😃

    Danke für den Ansatz ich werde es morgen früh sofort versuchen (bin heute nicht mehr auf der Arbeit)

    🙂


Anmelden zum Antworten