Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  dict_thesaurus.c   Sprache: unbekannt

 
/*-------------------------------------------------------------------------
 *
 * dict_thesaurus.c
 *  Thesaurus dictionary: phrase to phrase substitution
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *   src/backend/tsearch/dict_thesaurus.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/namespace.h"
#include "commands/defrem.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_public.h"
#include "utils/fmgrprotos.h"
#include "utils/regproc.h"


/*
 * Temporary we use TSLexeme.flags for inner use...
 */

#define DT_USEASIS  0x1000

typedef struct LexemeInfo
{
 uint32  idsubst;  /* entry's number in DictThesaurus->subst */
 uint16  posinsubst;  /* pos info in entry */
 uint16  tnvariant;  /* total num lexemes in one variant */
 struct LexemeInfo *nextentry;
 struct LexemeInfo *nextvariant;
} LexemeInfo;

typedef struct
{
 char    *lexeme;
 LexemeInfo *entries;
} TheLexeme;

typedef struct
{
 uint16  lastlexeme;  /* number lexemes to substitute */
 uint16  reslen;
 TSLexeme   *res;   /* prepared substituted result */
} TheSubstitute;

typedef struct
{
 /* subdictionary to normalize lexemes */
 Oid   subdictOid;
 TSDictionaryCacheEntry *subdict;

 /* Array to search lexeme by exact match */
 TheLexeme  *wrds;
 int   nwrds;   /* current number of words */
 int   ntwrds;   /* allocated array length */

 /*
  * Storage of substituted result, n-th element is for n-th expression
 */

 TheSubstitute *subst;
 int   nsubst;
} DictThesaurus;


static void
newLexeme(DictThesaurus *d, char *b, char *e, uint32 idsubst, uint16 posinsubst)
{
 TheLexeme  *ptr;

 if (d->nwrds >= d->ntwrds)
 {
  if (d->ntwrds == 0)
  {
   d->ntwrds = 16;
   d->wrds = (TheLexeme *) palloc(sizeof(TheLexeme) * d->ntwrds);
  }
  else
  {
   d->ntwrds *= 2;
   d->wrds = (TheLexeme *) repalloc(d->wrds, sizeof(TheLexeme) * d->ntwrds);
  }
 }

 ptr = d->wrds + d->nwrds;
 d->nwrds++;

 ptr->lexeme = palloc(e - b + 1);

 memcpy(ptr->lexeme, b, e - b);
 ptr->lexeme[e - b] = '\0';

 ptr->entries = (LexemeInfo *) palloc(sizeof(LexemeInfo));

 ptr->entries->nextentry = NULL;
 ptr->entries->idsubst = idsubst;
 ptr->entries->posinsubst = posinsubst;
}

static void
addWrd(DictThesaurus *d, char *b, char *e, uint32 idsubst, uint16 nwrd, uint16 posinsubst, bool useasis)
{
 static int nres = 0;
 static int ntres = 0;
 TheSubstitute *ptr;

 if (nwrd == 0)
 {
  nres = ntres = 0;

  if (idsubst >= d->nsubst)
  {
   if (d->nsubst == 0)
   {
    d->nsubst = 16;
    d->subst = (TheSubstitute *) palloc(sizeof(TheSubstitute) * d->nsubst);
   }
   else
   {
    d->nsubst *= 2;
    d->subst = (TheSubstitute *) repalloc(d->subst, sizeof(TheSubstitute) * d->nsubst);
   }
  }
 }

 ptr = d->subst + idsubst;

 ptr->lastlexeme = posinsubst - 1;

 if (nres + 1 >= ntres)
 {
  if (ntres == 0)
  {
   ntres = 2;
   ptr->res = (TSLexeme *) palloc(sizeof(TSLexeme) * ntres);
  }
  else
  {
   ntres *= 2;
   ptr->res = (TSLexeme *) repalloc(ptr->res, sizeof(TSLexeme) * ntres);
  }
 }

 ptr->res[nres].lexeme = palloc(e - b + 1);
 memcpy(ptr->res[nres].lexeme, b, e - b);
 ptr->res[nres].lexeme[e - b] = '\0';

 ptr->res[nres].nvariant = nwrd;
 if (useasis)
  ptr->res[nres].flags = DT_USEASIS;
 else
  ptr->res[nres].flags = 0;

 ptr->res[++nres].lexeme = NULL;
}

#define TR_WAITLEX 1
#define TR_INLEX 2
#define TR_WAITSUBS 3
#define TR_INSUBS 4

static void
thesaurusRead(const char *filename, DictThesaurus *d)
{
 tsearch_readline_state trst;
 uint32  idsubst = 0;
 bool  useasis = false;
 char    *line;

 filename = get_tsearch_config_filename(filename, "ths");
 if (!tsearch_readline_begin(&trst, filename))
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("could not open thesaurus file \"%s\": %m",
      filename)));

 while ((line = tsearch_readline(&trst)) != NULL)
 {
  char    *ptr;
  int   state = TR_WAITLEX;
  char    *beginwrd = NULL;
  uint32  posinsubst = 0;
  uint32  nwrd = 0;

  ptr = line;

  /* is it a comment? */
  while (*ptr && isspace((unsigned char) *ptr))
   ptr += pg_mblen_cstr(ptr);

  if (t_iseq(ptr, '#') || *ptr == '\0' ||
   t_iseq(ptr, '\n') || t_iseq(ptr, '\r'))
  {
   pfree(line);
   continue;
  }

  while (*ptr)
  {
   if (state == TR_WAITLEX)
   {
    if (t_iseq(ptr, ':'))
    {
     if (posinsubst == 0)
      ereport(ERROR,
        (errcode(ERRCODE_CONFIG_FILE_ERROR),
         errmsg("unexpected delimiter")));
     state = TR_WAITSUBS;
    }
    else if (!isspace((unsigned char) *ptr))
    {
     beginwrd = ptr;
     state = TR_INLEX;
    }
   }
   else if (state == TR_INLEX)
   {
    if (t_iseq(ptr, ':'))
    {
     newLexeme(d, beginwrd, ptr, idsubst, posinsubst++);
     state = TR_WAITSUBS;
    }
    else if (isspace((unsigned char) *ptr))
    {
     newLexeme(d, beginwrd, ptr, idsubst, posinsubst++);
     state = TR_WAITLEX;
    }
   }
   else if (state == TR_WAITSUBS)
   {
    if (t_iseq(ptr, '*'))
    {
     useasis = true;
     state = TR_INSUBS;
     beginwrd = ptr + pg_mblen_cstr(ptr);
    }
    else if (t_iseq(ptr, '\\'))
    {
     useasis = false;
     state = TR_INSUBS;
     beginwrd = ptr + pg_mblen_cstr(ptr);
    }
    else if (!isspace((unsigned char) *ptr))
    {
     useasis = false;
     beginwrd = ptr;
     state = TR_INSUBS;
    }
   }
   else if (state == TR_INSUBS)
   {
    if (isspace((unsigned char) *ptr))
    {
     if (ptr == beginwrd)
      ereport(ERROR,
        (errcode(ERRCODE_CONFIG_FILE_ERROR),
         errmsg("unexpected end of line or lexeme")));
     addWrd(d, beginwrd, ptr, idsubst, nwrd++, posinsubst, useasis);
     state = TR_WAITSUBS;
    }
   }
   else
    elog(ERROR, "unrecognized thesaurus state: %d", state);

   ptr += pg_mblen_cstr(ptr);
  }

  if (state == TR_INSUBS)
  {
   if (ptr == beginwrd)
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("unexpected end of line or lexeme")));
   addWrd(d, beginwrd, ptr, idsubst, nwrd++, posinsubst, useasis);
  }

  idsubst++;

  if (!(nwrd && posinsubst))
   ereport(ERROR,
     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      errmsg("unexpected end of line")));

  if (nwrd != (uint16) nwrd || posinsubst != (uint16) posinsubst)
   ereport(ERROR,
     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      errmsg("too many lexemes in thesaurus entry")));

  pfree(line);
 }

 d->nsubst = idsubst;

 tsearch_readline_end(&trst);
}

static TheLexeme *
addCompiledLexeme(TheLexeme *newwrds, int *nnw, int *tnm, TSLexeme *lexeme, LexemeInfo *src, uint16 tnvariant)
{
 if (*nnw >= *tnm)
 {
  *tnm *= 2;
  newwrds = (TheLexeme *) repalloc(newwrds, sizeof(TheLexeme) * *tnm);
 }

 newwrds[*nnw].entries = (LexemeInfo *) palloc(sizeof(LexemeInfo));

 if (lexeme && lexeme->lexeme)
 {
  newwrds[*nnw].lexeme = pstrdup(lexeme->lexeme);
  newwrds[*nnw].entries->tnvariant = tnvariant;
 }
 else
 {
  newwrds[*nnw].lexeme = NULL;
  newwrds[*nnw].entries->tnvariant = 1;
 }

 newwrds[*nnw].entries->idsubst = src->idsubst;
 newwrds[*nnw].entries->posinsubst = src->posinsubst;

 newwrds[*nnw].entries->nextentry = NULL;

 (*nnw)++;
 return newwrds;
}

static int
cmpLexemeInfo(LexemeInfo *a, LexemeInfo *b)
{
 if (a == NULL || b == NULL)
  return 0;

 if (a->idsubst == b->idsubst)
 {
  if (a->posinsubst == b->posinsubst)
  {
   if (a->tnvariant == b->tnvariant)
    return 0;

   return (a->tnvariant > b->tnvariant) ? 1 : -1;
  }

  return (a->posinsubst > b->posinsubst) ? 1 : -1;
 }

 return (a->idsubst > b->idsubst) ? 1 : -1;
}

static int
cmpLexeme(const TheLexeme *a, const TheLexeme *b)
{
 if (a->lexeme == NULL)
 {
  if (b->lexeme == NULL)
   return 0;
  else
   return 1;
 }
 else if (b->lexeme == NULL)
  return -1;

 return strcmp(a->lexeme, b->lexeme);
}

static int
cmpLexemeQ(const void *a, const void *b)
{
 return cmpLexeme((const TheLexeme *) a, (const TheLexeme *) b);
}

static int
cmpTheLexeme(const void *a, const void *b)
{
 const TheLexeme *la = (const TheLexeme *) a;
 const TheLexeme *lb = (const TheLexeme *) b;
 int   res;

 if ((res = cmpLexeme(la, lb)) != 0)
  return res;

 return -cmpLexemeInfo(la->entries, lb->entries);
}

static void
compileTheLexeme(DictThesaurus *d)
{
 int   i,
    nnw = 0,
    tnm = 16;
 TheLexeme  *newwrds = (TheLexeme *) palloc(sizeof(TheLexeme) * tnm),
      *ptrwrds;

 for (i = 0; i < d->nwrds; i++)
 {
  TSLexeme   *ptr;

  if (strcmp(d->wrds[i].lexeme, "?") == 0/* Is stop word marker? */
   newwrds = addCompiledLexeme(newwrds, &nnw, &tnm, NULL, d->wrds[i].entries, 0);
  else
  {
   ptr = (TSLexeme *) DatumGetPointer(FunctionCall4(&(d->subdict->lexize),
                PointerGetDatum(d->subdict->dictData),
                PointerGetDatum(d->wrds[i].lexeme),
                Int32GetDatum(strlen(d->wrds[i].lexeme)),
                PointerGetDatum(NULL)));

   if (!ptr)
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("thesaurus sample word \"%s\" isn't recognized by subdictionary (rule %d)",
        d->wrds[i].lexeme,
        d->wrds[i].entries->idsubst + 1)));
   else if (!(ptr->lexeme))
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("thesaurus sample word \"%s\" is a stop word (rule %d)",
        d->wrds[i].lexeme,
        d->wrds[i].entries->idsubst + 1),
       errhint("Use \"?\" to represent a stop word within a sample phrase.")));
   else
   {
    while (ptr->lexeme)
    {
     TSLexeme   *remptr = ptr + 1;
     int   tnvar = 1;
     int   curvar = ptr->nvariant;

     /* compute n words in one variant */
     while (remptr->lexeme)
     {
      if (remptr->nvariant != (remptr - 1)->nvariant)
       break;
      tnvar++;
      remptr++;
     }

     remptr = ptr;
     while (remptr->lexeme && remptr->nvariant == curvar)
     {
      newwrds = addCompiledLexeme(newwrds, &nnw, &tnm, remptr, d->wrds[i].entries, tnvar);
      remptr++;
     }

     ptr = remptr;
    }
   }
  }

  pfree(d->wrds[i].lexeme);
  pfree(d->wrds[i].entries);
 }

 if (d->wrds)
  pfree(d->wrds);
 d->wrds = newwrds;
 d->nwrds = nnw;
 d->ntwrds = tnm;

 if (d->nwrds > 1)
 {
  qsort(d->wrds, d->nwrds, sizeof(TheLexeme), cmpTheLexeme);

  /* uniq */
  newwrds = d->wrds;
  ptrwrds = d->wrds + 1;
  while (ptrwrds - d->wrds < d->nwrds)
  {
   if (cmpLexeme(ptrwrds, newwrds) == 0)
   {
    if (cmpLexemeInfo(ptrwrds->entries, newwrds->entries))
    {
     ptrwrds->entries->nextentry = newwrds->entries;
     newwrds->entries = ptrwrds->entries;
    }
    else
     pfree(ptrwrds->entries);

    if (ptrwrds->lexeme)
     pfree(ptrwrds->lexeme);
   }
   else
   {
    newwrds++;
    *newwrds = *ptrwrds;
   }

   ptrwrds++;
  }

  d->nwrds = newwrds - d->wrds + 1;
  d->wrds = (TheLexeme *) repalloc(d->wrds, sizeof(TheLexeme) * d->nwrds);
 }
}

static void
compileTheSubstitute(DictThesaurus *d)
{
 int   i;

 for (i = 0; i < d->nsubst; i++)
 {
  TSLexeme   *rem = d->subst[i].res,
       *outptr,
       *inptr;
  int   n = 2;

  outptr = d->subst[i].res = (TSLexeme *) palloc(sizeof(TSLexeme) * n);
  outptr->lexeme = NULL;
  inptr = rem;

  while (inptr && inptr->lexeme)
  {
   TSLexeme   *lexized,
      tmplex[2];

   if (inptr->flags & DT_USEASIS)
   {     /* do not lexize */
    tmplex[0] = *inptr;
    tmplex[0].flags = 0;
    tmplex[1].lexeme = NULL;
    lexized = tmplex;
   }
   else
   {
    lexized = (TSLexeme *) DatumGetPointer(FunctionCall4(&(d->subdict->lexize),
                  PointerGetDatum(d->subdict->dictData),
                  PointerGetDatum(inptr->lexeme),
                  Int32GetDatum(strlen(inptr->lexeme)),
                  PointerGetDatum(NULL)));
   }

   if (lexized && lexized->lexeme)
   {
    int   toset = (lexized->lexeme && outptr != d->subst[i].res) ? (outptr - d->subst[i].res) : -1;

    while (lexized->lexeme)
    {
     if (outptr - d->subst[i].res + 1 >= n)
     {
      int   diff = outptr - d->subst[i].res;

      n *= 2;
      d->subst[i].res = (TSLexeme *) repalloc(d->subst[i].res, sizeof(TSLexeme) * n);
      outptr = d->subst[i].res + diff;
     }

     *outptr = *lexized;
     outptr->lexeme = pstrdup(lexized->lexeme);

     outptr++;
     lexized++;
    }

    if (toset > 0)
     d->subst[i].res[toset].flags |= TSL_ADDPOS;
   }
   else if (lexized)
   {
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("thesaurus substitute word \"%s\" is a stop word (rule %d)",
        inptr->lexeme, i + 1)));
   }
   else
   {
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("thesaurus substitute word \"%s\" isn't recognized by subdictionary (rule %d)",
        inptr->lexeme, i + 1)));
   }

   if (inptr->lexeme)
    pfree(inptr->lexeme);
   inptr++;
  }

  if (outptr == d->subst[i].res)
   ereport(ERROR,
     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      errmsg("thesaurus substitute phrase is empty (rule %d)",
       i + 1)));

  d->subst[i].reslen = outptr - d->subst[i].res;

  pfree(rem);
 }
}

Datum
thesaurus_init(PG_FUNCTION_ARGS)
{
 List    *dictoptions = (List *) PG_GETARG_POINTER(0);
 DictThesaurus *d;
 char    *subdictname = NULL;
 bool  fileloaded = false;
 List    *namelist;
 ListCell   *l;

 d = (DictThesaurus *) palloc0(sizeof(DictThesaurus));

 foreach(l, dictoptions)
 {
  DefElem    *defel = (DefElem *) lfirst(l);

  if (strcmp(defel->defname, "dictfile") == 0)
  {
   if (fileloaded)
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
       errmsg("multiple DictFile parameters")));
   thesaurusRead(defGetString(defel), d);
   fileloaded = true;
  }
  else if (strcmp(defel->defname, "dictionary") == 0)
  {
   if (subdictname)
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
       errmsg("multiple Dictionary parameters")));
   subdictname = pstrdup(defGetString(defel));
  }
  else
  {
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      errmsg("unrecognized Thesaurus parameter: \"%s\"",
       defel->defname)));
  }
 }

 if (!fileloaded)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     errmsg("missing DictFile parameter")));
 if (!subdictname)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     errmsg("missing Dictionary parameter")));

 namelist = stringToQualifiedNameList(subdictname, NULL);
 d->subdictOid = get_ts_dict_oid(namelist, false);
 d->subdict = lookup_ts_dictionary_cache(d->subdictOid);

 compileTheLexeme(d);
 compileTheSubstitute(d);

 PG_RETURN_POINTER(d);
}

static LexemeInfo *
findTheLexeme(DictThesaurus *d, char *lexeme)
{
 TheLexeme key,
      *res;

 if (d->nwrds == 0)
  return NULL;

 key.lexeme = lexeme;
 key.entries = NULL;

 res = bsearch(&key, d->wrds, d->nwrds, sizeof(TheLexeme), cmpLexemeQ);

 if (res == NULL)
  return NULL;
 return res->entries;
}

static bool
matchIdSubst(LexemeInfo *stored, uint32 idsubst)
{
 bool  res = true;

 if (stored)
 {
  res = false;

  for (; stored; stored = stored->nextvariant)
   if (stored->idsubst == idsubst)
   {
    res = true;
    break;
   }
 }

 return res;
}

static LexemeInfo *
findVariant(LexemeInfo *in, LexemeInfo *stored, uint16 curpos, LexemeInfo **newin, int newn)
{
 for (;;)
 {
  int   i;
  LexemeInfo *ptr = newin[0];

  for (i = 0; i < newn; i++)
  {
   while (newin[i] && newin[i]->idsubst < ptr->idsubst)
    newin[i] = newin[i]->nextentry;

   if (newin[i] == NULL)
    return in;

   if (newin[i]->idsubst > ptr->idsubst)
   {
    ptr = newin[i];
    i = -1;
    continue;
   }

   while (newin[i]->idsubst == ptr->idsubst)
   {
    if (newin[i]->posinsubst == curpos && newin[i]->tnvariant == newn)
    {
     ptr = newin[i];
     break;
    }

    newin[i] = newin[i]->nextentry;
    if (newin[i] == NULL)
     return in;
   }

   if (newin[i]->idsubst != ptr->idsubst)
   {
    ptr = newin[i];
    i = -1;
    continue;
   }
  }

  if (i == newn && matchIdSubst(stored, ptr->idsubst) && (in == NULL || !matchIdSubst(in, ptr->idsubst)))
  {      /* found */

   ptr->nextvariant = in;
   in = ptr;
  }

  /* step forward */
  for (i = 0; i < newn; i++)
   newin[i] = newin[i]->nextentry;
 }
}

static TSLexeme *
copyTSLexeme(TheSubstitute *ts)
{
 TSLexeme   *res;
 uint16  i;

 res = (TSLexeme *) palloc(sizeof(TSLexeme) * (ts->reslen + 1));
 for (i = 0; i < ts->reslen; i++)
 {
  res[i] = ts->res[i];
  res[i].lexeme = pstrdup(ts->res[i].lexeme);
 }

 res[ts->reslen].lexeme = NULL;

 return res;
}

static TSLexeme *
checkMatch(DictThesaurus *d, LexemeInfo *info, uint16 curpos, bool *moreres)
{
 *moreres = false;
 while (info)
 {
  Assert(info->idsubst < d->nsubst);
  if (info->nextvariant)
   *moreres = true;
  if (d->subst[info->idsubst].lastlexeme == curpos)
   return copyTSLexeme(d->subst + info->idsubst);
  info = info->nextvariant;
 }

 return NULL;
}

Datum
thesaurus_lexize(PG_FUNCTION_ARGS)
{
 DictThesaurus *d = (DictThesaurus *) PG_GETARG_POINTER(0);
 DictSubState *dstate = (DictSubState *) PG_GETARG_POINTER(3);
 TSLexeme   *res = NULL;
 LexemeInfo *stored,
      *info = NULL;
 uint16  curpos = 0;
 bool  moreres = false;

 if (PG_NARGS() != 4 || dstate == NULL)
  elog(ERROR, "forbidden call of thesaurus or nested call");

 if (dstate->isend)
  PG_RETURN_POINTER(NULL);
 stored = (LexemeInfo *) dstate->private_state;

 if (stored)
  curpos = stored->posinsubst + 1;

 if (!d->subdict->isvalid)
  d->subdict = lookup_ts_dictionary_cache(d->subdictOid);

 res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(d->subdict->lexize),
              PointerGetDatum(d->subdict->dictData),
              PG_GETARG_DATUM(1),
              PG_GETARG_DATUM(2),
              PointerGetDatum(NULL)));

 if (res && res->lexeme)
 {
  TSLexeme   *ptr = res,
       *basevar;

  while (ptr->lexeme)
  {
   uint16  nv = ptr->nvariant;
   uint16  i,
      nlex = 0;
   LexemeInfo **infos;

   basevar = ptr;
   while (ptr->lexeme && nv == ptr->nvariant)
   {
    nlex++;
    ptr++;
   }

   infos = (LexemeInfo **) palloc(sizeof(LexemeInfo *) * nlex);
   for (i = 0; i < nlex; i++)
    if ((infos[i] = findTheLexeme(d, basevar[i].lexeme)) == NULL)
     break;

   if (i < nlex)
   {
    /* no chance to find */
    pfree(infos);
    continue;
   }

   info = findVariant(info, stored, curpos, infos, nlex);
  }
 }
 else if (res)
 {       /* stop-word */
  LexemeInfo *infos = findTheLexeme(d, NULL);

  info = findVariant(NULL, stored, curpos, &infos, 1);
 }
 else
 {
  info = NULL;   /* word isn't recognized */
 }

 dstate->private_state = info;

 if (!info)
 {
  dstate->getnext = false;
  PG_RETURN_POINTER(NULL);
 }

 if ((res = checkMatch(d, info, curpos, &moreres)) != NULL)
 {
  dstate->getnext = moreres;
  PG_RETURN_POINTER(res);
 }

 dstate->getnext = true;

 PG_RETURN_POINTER(NULL);
}

Messung V0.5 in Prozent
C=93 H=95 G=93

[0.7QuellennavigatorsProjekt 2026-08-08]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002