Thread: user defined type

user defined type

From
Kjetil Haaland
Date:
Hello

I am writing my own type for use in postgres. I want to be able to save a
value - an integer, and a string in this type. I have looked at the
documentation for how to do this. The insert into the type works, but when i
try to select all from the table, postgres crashes. I don't see what's wrong,
so I hope someone can help.

thanks
-Kjetil

the code:
typedef struct alignres {
  int value;
  char *fstring;
}alignres;
PG_FUNCTION_INFO_V1(alignres_in);

Datum alignres_in(PG_FUNCTION_ARGS) {
  char *in = PG_GETARG_CSTRING(0);

  int v;
  char *first;

  alignres *result;

  if(sscanf(in, "(%d, %s)", &v, &first) != 2) {
    ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
            errmsg("invalid input type for alignment: \"%s\"", in)));
  }
  result = (alignres *) palloc(sizeof(alignres));
  result->value = v;
  result->fstring = first;

  PG_RETURN_POINTER(result);
}

PG_FUNCTION_INFO_V1(alignres_out);

Datum alignres_out(PG_FUNCTION_ARGS) {
  alignres *align = (alignres *) PG_GETARG_POINTER(0);
  char *result;

  char temp[4+sizeof(align->fstring)+4+4];

  sprintf(temp, "(%d, %s)",
     align->value,
     align->fstring);

  result = (char*) palloc(strlen(temp)+1);
  strcpy(result, temp);
  PG_RETURN_CSTRING(result);
}

Re: user defined type

From
Michael Fuhr
Date:
On Mon, Nov 08, 2004 at 01:26:55PM +0100, Kjetil Haaland wrote:

> typedef struct alignres {
>   int value;
>   char *fstring;
> }alignres;

If you want to store character data, I think the characters must
be stored in the structure itself; I don't think using a pointer
will work (somebody please correct me if I'm mistaken).  So instead
of "char *fstring" you'll need "char fstring[x]", where "x" is the
maximum size of the character data.  You could also use "char fstring[1]"
and allocate the alignres structure to be as big as necessary; if
you do that then you might want to review the paragraph regarding
TOAST-able types near the end of the "User-Defined Types" section
of the documentation.

http://www.postgresql.org/docs/7.4/static/xtypes.html

Make sure your CREATE TYPE statement has "internallength" set
correctly: either to the fixed size of the structure (including
character data) or to "variable".  If you use "variable" then make
sure you add the required length field to the beginning of the
structure.  See the CREATE TYPE documentation for more info.

http://www.postgresql.org/docs/7.4/static/sql-createtype.html

>   if(sscanf(in, "(%d, %s)", &v, &first) != 2) {

The above line has several problems:

* For a %s conversion, sscanf() expects a pointer to character;
earlier you declared first as a char *, so you're passing a pointer
to a pointer to character.  sscanf() also expects the pointer to
point to a buffer sufficiently large to hold the result; you haven't
allocated any space.

* %s is greedy, so it'll grab the closing parenthesis in addition
to what precedes it.  Consider using %[ instead or make sure you
strip extraneous characters from the end of the result string.

* Your %s conversion doesn't specify a maximum field width, so
you risk a buffer overflow if the result buffer isn't at least
as large as the input string.

Additionally, consider using %[ instead of %d for the first field
and store the result in a character array so you can do better
validity checking than sscanf().  For example, you could then use
strtol() to check for overflow -- with sscanf() you'd just get bogus
numbers that don't match the input.

>   char temp[4+sizeof(align->fstring)+4+4];

This line in your output function might not be allocating as much
space as you think.  The sizeof expression evaluates to the size
of a pointer, not to the length of the data pointed to.  You're
probably allocating about 16 bytes here, which might be less
than you need.

>   sprintf(temp, "(%d, %s)",
>      align->value,
>      align->fstring);

By using sprintf() instead of snprintf() you again risk a buffer
overflow, especially since temp might be smaller than you were
expecting.

There might be other problems but those stood out when I looked
through the code.  If you still have trouble then please post
the updated code and describe what happens when you try to use
the type.

Hope this helps.

--
Michael Fuhr
http://www.fuhr.org/~mfuhr/

Re: user defined type

From
Kjetil Haaland
Date:
On Monday 08 November 2004 22:24, Michael Fuhr wrote:
> On Mon, Nov 08, 2004 at 01:26:55PM +0100, Kjetil Haaland wrote:
> > typedef struct alignres {
> >   int value;
> >   char *fstring;
> > }alignres;
>
> If you want to store character data, I think the characters must
> be stored in the structure itself; I don't think using a pointer
> will work (somebody please correct me if I'm mistaken).  So instead
> of "char *fstring" you'll need "char fstring[x]", where "x" is the
> maximum size of the character data.  You could also use "char fstring[1]"
> and allocate the alignres structure to be as big as necessary; if
> you do that then you might want to review the paragraph regarding
> TOAST-able types near the end of the "User-Defined Types" section
> of the documentation.
>
> http://www.postgresql.org/docs/7.4/static/xtypes.html
>
> Make sure your CREATE TYPE statement has "internallength" set
> correctly: either to the fixed size of the structure (including
> character data) or to "variable".  If you use "variable" then make
> sure you add the required length field to the beginning of the
> structure.  See the CREATE TYPE documentation for more info.

Thanks for the answer. There are some stuff i don't understand. I want to use
the fstring[1] way, because i don't know how large my string will be. You say
that i can allocate the hole structure, where should i do this? in the
in-data-function? It also says in the documentation that if I use the random
for internallength when i create my type I have to set the length in the
structure. Is this set in the declaration of the structure or in some of the
methods?

-Kjetil

Re: user defined type

From
Michael Fuhr
Date:
On Tue, Nov 09, 2004 at 01:38:29PM +0100, Kjetil Haaland wrote:
> On Monday 08 November 2004 22:24, Michael Fuhr wrote:
> > On Mon, Nov 08, 2004 at 01:26:55PM +0100, Kjetil Haaland wrote:
> > > typedef struct alignres {
> > >   int value;
> > >   char *fstring;
> > > }alignres;
> >
> > If you want to store character data, I think the characters must
> > be stored in the structure itself; I don't think using a pointer
> > will work (somebody please correct me if I'm mistaken).  So instead
> > of "char *fstring" you'll need "char fstring[x]", where "x" is the
> > maximum size of the character data.  You could also use "char fstring[1]"
> > and allocate the alignres structure to be as big as necessary; if
> > you do that then you might want to review the paragraph regarding
> > TOAST-able types near the end of the "User-Defined Types" section
> > of the documentation.
> >
> > http://www.postgresql.org/docs/7.4/static/xtypes.html
> >
> > Make sure your CREATE TYPE statement has "internallength" set
> > correctly: either to the fixed size of the structure (including
> > character data) or to "variable".  If you use "variable" then make
> > sure you add the required length field to the beginning of the
> > structure.  See the CREATE TYPE documentation for more info.

> I want to use the fstring[1] way, because i don't know how large
> my string will be. You say that i can allocate the hole structure,
> where should i do this? in the in-data-function?

Yes, in alignres_in() you'd allocate enough memory to hold all of
the data: the size of the structure plus the length of the string.
If you don't mind a little waste then here's a simple way to do it:

  char *in = PG_GETARG_CSTRING(0);
  alignres *result;

  result = palloc(sizeof(*result) + strlen(in));

(Note that the compiler evalutes sizeof(*result), so you don't have
to worry that you're dereferencing an unassigned pointer at run
time.  This syntax removes the need to specify the type name again,
which eases maintenance.)

I don't think you need to add an extra byte to allow for the string's
terminating NUL because sizeof(*result) already accounts for one
byte of the string (and probably a little extra, due to alignment).
Double-check my thinking, though, and add an extra byte if you think
it's necessary.

> It also says in the documentation that if I use the random for
> internallength when i create my type I have to set the length in
> the structure. Is this set in the declaration of the structure or
> in some of the methods?

The internallength specification is "variable", not "random."  The
length is an int32 at the beginning of the data:

typedef struct alignres {
  int32 length;
  int   value;
  char  fstring[1];
} alignres;

Before returning, set the length field to the total size of the data.
Here's where I think you could eliminate any wasted space that you
allocated -- after you've parsed the data you know exactly how long
fstring is, so you could add only that much to sizeof(*result):

result->length = sizeof(*result) + strlen(fstring);

Again, I don't think you need to add an extra byte to account for
the string's terminating NUL because sizeof(*result) already includes
one byte for the string.

--
Michael Fuhr
http://www.fuhr.org/~mfuhr/

Re: user defined type

From
Kjetil Haaland
Date:
hello again

Thanks to you i have no managed to store a value and a string of variable
length in the structure. I thought it would be easy to expand it to two
strings and a value, by doing it the same way, but that didn't work out. When
i insert something into the type this is what happends:

1) the first string in the structure has one character( or the size of the
table that holds the first string) from the first string i gave, a blank
space and the second string i gave.
2)the second string is correct - has the second string i gave.

Is there anyway to have two or more strings in one type or do i have to save
them both in one string? This is my type and the in function so far:

typedef struct alignres {
  int32 length;
  int value;
  char fstring[1];
  char sstring[1];
}alignres;

Datum alignres_in(PG_FUNCTION_ARGS) {
  char *in = PG_GETARG_CSTRING(0);
  char *workstr = pstrdup(in);
  char *svalue = NULL;
  char *first = NULL;
  char *second = NULL;

  svalue = strtok(workstr, ", ");
  first = strtok(NULL, ", ");
  second = strtok(NULL, ")");

  int value = atoi(++svalue);
  alignres *result;
  result = (alignres *) palloc(sizeof(*result)+strlen(in));

  result->value = value;
  strcpy(result->fstring, first);
  strcpy(result->sstring, second);
  result->length=sizeof(*result)+strlen(result->fstring)+strlen(result->sstring);

  PG_RETURN_POINTER(result);
}

-Kjetil

Re: user defined type

From
Michael Fuhr
Date:
On Wed, Nov 10, 2004 at 02:37:49PM +0100, Kjetil Haaland wrote:

> Is there anyway to have two or more strings in one type or do i have to save
> them both in one string?

PostgreSQL needs all the data in a single buffer.  If you want to
work with multiple variable-length fields, then you could define a
structure that's easy for your code to work with and serialize it
before returning it to PostgreSQL, or one that uses pointers that
point to the correct places in the buffer.  Your alignres_out()
function will need to reverse that effect so it can work with the
data that PostgreSQL passes it.

--
Michael Fuhr
http://www.fuhr.org/~mfuhr/

Re: user defined type

From
Kjetil Haaland
Date:
On Wednesday 10 November 2004 19:30, Michael Fuhr wrote:
> On Wed, Nov 10, 2004 at 02:37:49PM +0100, Kjetil Haaland wrote:
> > Is there anyway to have two or more strings in one type or do i have to
> > save them both in one string?
>
> PostgreSQL needs all the data in a single buffer.  If you want to
> work with multiple variable-length fields, then you could define a
> structure that's easy for your code to work with and serialize it
> before returning it to PostgreSQL, or one that uses pointers that
> point to the correct places in the buffer.  Your alignres_out()
> function will need to reverse that effect so it can work with the
> data that PostgreSQL passes it.

Hello again
Thanks a lot for all the help!
I have one more question about user defined types. When i create a char
pointer in the alignres_out (the out function of the type) function why is it
always default "alignres_out" ? Is it supposed to be that or have i done
something wrong?

an example:
Datum alignres_out(PG_FUNCTION_ARGS) {
    char *result;
    elog(NOTICE, "result=%s", result);
}
This will give me NOTICE: result=alignres_out.
It is not just in the output function this happens, it is the same in the
input - alignres_in gives default alignres_in.

- Kjetil

Re: user defined type

From
Tom Lane
Date:
Kjetil Haaland <kjetil.haaland@student.uib.no> writes:
> an example:
> Datum alignres_out(PG_FUNCTION_ARGS) {
>     char *result;
>     elog(NOTICE, "result=%s", result);
> }

This code is wrong on its face.  If your compiler doesn't give a warning
along the lines of "use of uninitialized value", get a better compiler.
(Note: when using gcc I think you must specify -O to get this warning.
I always use at least -O -Wall when developing with gcc.)

> This will give me NOTICE: result=alignres_out.

Pure luck that it doesn't crash instead.  Apparently you're picking up a
value that happened to be left in a register by the function-call
manager, but on another machine or after any slight mod to fmgr.c that
register might contain something that's not a pointer to string at all.

            regards, tom lane

Re: user defined type

From
Kjetil Haaland
Date:
On Friday 19 November 2004 16:16, Tom Lane wrote:
> This code is wrong on its face.  If your compiler doesn't give a warning
> along the lines of "use of uninitialized value", get a better compiler.
> (Note: when using gcc I think you must specify -O to get this warning.
> I always use at least -O -Wall when developing with gcc.)
>
> > This will give me NOTICE: result=alignres_out.
>
> Pure luck that it doesn't crash instead.  Apparently you're picking up a
> value that happened to be left in a register by the function-call
> manager, but on another machine or after any slight mod to fmgr.c that
> register might contain something that's not a pointer to string at all.
>
>             regards, tom lane

Ok, the example wasnt complete, i just tried to show what i meant, so i try
again with the hole example. Now i use the \O and it gives no warnings.

Datum alignres_out(PG_FUNCTION_ARGS) {
   alignres *align = (alignres *) PG_GETARG_POINTER(0);
   char *result = NULL;
   int secondStart=align->secondString;
   elog(NOTICE, "secondStart=%d", secondStart);
   char temp[4+strlen(align->stringBuffer)+4+4];
   char tempBuffer[strlen(align->stringBuffer)+1];

   char *first = NULL;
   elog(NOTICE, "1:first=%s", first);
   first = palloc(sizeof(char)*secondStart);
   elog(NOTICE, "2:first=%s", first);
   elog(NOTICE, "strlen(first)=%d", strlen(first));

   snprintf(tempBuffer, sizeof(tempBuffer), "%s", align->stringBuffer);
   first = strncpy(first, tempBuffer, secondStart-1);
   snprintf(temp, sizeof(temp), "(%d, %s)",
      align->value,
      first);
   result = (char*) palloc(strlen(temp)+1);
   strcpy(result, temp);
   PG_RETURN_CSTRING(result);
}

On the first elog i get (null) and thats correct. After the allocation i get
alignres_out. When i print out the variable secondString it is 5, but when i
print out the length of the first it is set to 12, the length of
"alignres_out".

Am i still doing something wrong? If so, how am i supposed to do it to get it
correct?

-Kjetil

Re: user defined type

From
Tom Lane
Date:
Kjetil Haaland <kjetil.haaland@student.uib.no> writes:
>    first = palloc(sizeof(char)*secondStart);
>    elog(NOTICE, "2:first=%s", first);
>    elog(NOTICE, "strlen(first)=%d", strlen(first));

This is bogus because you've only allocated space, not filled it with
anything.  It will contain whatever that RAM was last used for.

            regards, tom lane