Thread: INTERVAL data type and libpq - what format?

INTERVAL data type and libpq - what format?

From
Sebastien FLAESCH
Date:
Hello,

I try to use the new 8.4 INTERVAL type with libpq, but get crazy with
the input formatting rules...

I use PQprepare() / PQexecPrepared() with parameter list, binding the
INTERVAL values with the 1186 pg_type and passing a string buffer with
values like:

"12345"   for an INTERVAL YEAR

The INSERT works without error, but when selecting rows from the table
in psql, I get "00:00:00" values ?!?!

When inserting the value "12345" from the psql command tool it works...

I must be doing something wrong, but I could not find any documentation
on using INTERVAL in libpq...

Can someone from the hackers just tell me if it's supposed to work and
if yes what format is expected by the client library?

Attached, you have a test case to reproduce the problem.

Thanks a lot!
Seb
/*
Version:    8.4.beta1
Created by: sf@4js.com

Problem with INTERVAL input format
----------------------------------

After executing this program, 2 rows are present in the table.
Only the first has the expected values...

Why does the second insert fail to insert "123 11" in INTERVAL DAY TO HOUR?
Diagnostic info:
  SQL State: 22007
  Message  : invalid input syntax for type interval: " 123 11"

Why does the third row show "00:00:00" in first INTERVAL YEAR column?

[sf@fox problems]$ psql test1 -U pgsuser
psql (8.4beta1)
Type "help" for help.

test1=> select * from t1;
 k |      i1      |        i2
---+--------------+-------------------
 1 | -12345 years | 123 days 11:00:00
 3 | 00:00:00     | 123 days 11:00:00
(2 rows)

When inserting rows with psql, the format used by the C program are supported:

test1=> insert into t1 values ( 4, '-12345', '123 11' );
INSERT 0 1
test1=> select * from t1 where k=4;
 k |      i1      |        i2
---+--------------+-------------------
 4 | -12345 years | 123 days 11:00:00
(1 row)

So what am I doing wrong here?

*/

#include <stdio.h>
#include <libpq-fe.h>

static int checkResult(PGresult * r)
{
    if (r == NULL)
        return 0;
    switch (PQresultStatus(r)) {
    case PGRES_COMMAND_OK:
    case PGRES_TUPLES_OK:
        return 1;
    default:
        return 0;
    }
}

static void getErrorInfo(PGresult * r)
{
    if (r == NULL)
       return;
    fprintf(stderr, "Diagnostic info:\n");
    fprintf(stderr, "  SQL State: %s\n", PQresultErrorField(r, PG_DIAG_SQLSTATE));
    fprintf(stderr, "  Message  : %s\n", PQresultErrorField(r, PG_DIAG_MESSAGE_PRIMARY));
}

int main(int argc, char **argv)
{
    PGresult *r;
    PGconn *c;
    Oid paramTypes[10];
    const char *paramValues[10];

    fprintf(stdout,"++ Connecting...\n");
    c = PQconnectdb("dbname='test1' user='pgsuser' password='fourjs'");
    if (c == NULL) {
        fprintf(stderr,">> Could not connect.\n");
        exit(1);
    }

    fprintf(stdout,"++ Creating table t1 ...\n");
    r = PQexec(c, "DROP TABLE t1");
    PQclear(r);
    r = PQexec(c, "CREATE TABLE t1 ( k INT, i1 INTERVAL YEAR, i2 INTERVAL DAY TO HOUR)");
    if (!checkResult(r)) {
        fprintf(stderr,">> Could not create table 1.\n");
        getErrorInfo(r);
        exit(1);
    }
    PQclear(r);

    fprintf(stdout,"++ Preparing INSERT ...\n");
    paramTypes[0] = 23;     /* INT4 */
    paramTypes[1] = 1186;   /* INTERVAL */
    paramTypes[2] = 1186;   /* INTERVAL */
    r = PQprepare(c, "s1",
                  "INSERT INTO t1 VALUES ( $1, $2, $3 )",
                  3, (const Oid *) paramTypes);
    if (!checkResult(r)) {
        fprintf(stderr,">> Could not prepare stmt 1.\n");
        getErrorInfo(r);
        exit(1);
    }
    PQclear(r);

    /* This is working */
    fprintf(stdout,"++ Executing INSERT (1) ...\n");
    paramValues[0] = "1";
    paramValues[1] = "-12345 years";
    paramValues[2] = " 123 11:00";
    r = PQexecPrepared(c, "s1", 3, paramValues, NULL, NULL, 0);
    if (!checkResult(r)) {
        fprintf(stderr,">> Could not exec stmt 1.\n");
        getErrorInfo(r);
        exit(1);
    }
    PQclear(r);

    /* This is NOT working */
    fprintf(stdout,"++ Executing INSERT (2) ...\n");
    paramValues[0] = "2";
    paramValues[1] = "-12345";
    paramValues[2] = " 123 11";
    r = PQexecPrepared(c, "s1", 3, paramValues, NULL, NULL, 0);
    if (!checkResult(r)) {
        fprintf(stderr,">> Could not exec stmt 2.\n");
        getErrorInfo(r);
        /*exit(1);*/
    }
    PQclear(r);

    /* This is NOT working */
    fprintf(stdout,"++ Executing INSERT (3) ...\n");
    paramValues[0] = "3";
    paramValues[1] = "-12345";
    paramValues[2] = " 123 11:00";
    r = PQexecPrepared(c, "s1", 3, paramValues, NULL, NULL, 0);
    if (!checkResult(r)) {
        fprintf(stderr,">> Could not exec stmt 3.\n");
        getErrorInfo(r);
        exit(1);
    }
    PQclear(r);

    PQfinish(c);
}

Re: INTERVAL data type and libpq - what format?

From
Sam Mason
Date:
On Tue, May 19, 2009 at 10:08:37AM +0200, Sebastien FLAESCH wrote:
> I try to use the new 8.4 INTERVAL type with libpq, but get crazy with
> the input formatting rules...

I think you're giving the database conflicting instructions and it's
getting confused.

>     fprintf(stdout,"++ Preparing INSERT ...\n");
>     paramTypes[0] = 23;     /* INT4 */
>     paramTypes[1] = 1186;   /* INTERVAL */
>     paramTypes[2] = 1186;   /* INTERVAL */

I don't really know 8.4, but I believe you're saying here that you
explicitly want the values to be of basic INTERVAL type here, i.e. not
INTERVAL DAY TO HOUR for parameter 3.

Thus when you do:

>     paramValues[0] = "1";
>     paramValues[1] = "-12345 years";
>     paramValues[2] = " 123 11:00";
>     r = PQexecPrepared(c, "s1", 3, paramValues, NULL, NULL, 0);

It's interpreting " 123 11:00" correctly as a basic INTERVAL value and
then casting it to your more constrained version as you're saving in the
table.

However, when you do:

>     paramValues[0] = "2";
>     paramValues[1] = "-12345";
>     paramValues[2] = " 123 11";
>     r = PQexecPrepared(c, "s1", 3, paramValues, NULL, NULL, 0);

You get an error because " 123 11" isn't a valid literal of an
(undecorated) INTERVAL type.  I think PG may do the right thing if you
don't specify the types when preparing the query, but haven't tested.


--
  Sam  http://samason.me.uk/

Re: INTERVAL data type and libpq - what format?

From
Sebastien FLAESCH
Date:
Yes, good point.

I realize now that I would have expected libpq to give me a way to specify
the exact decoration or precision of INTERVAL parameters...

As you can do with ODBC's SQLBindParameter(), where you specify the C type,
SQL type, precision/scale or length ...
I believe this is important when it comes to data type conversion (for ex,
when you want to insert a numeric/date/time into a char or the other way).
=> sort of cast, actually...

I known libpq functions like PQexecParams() or PQexecPrepared() have a
paramFormats[] parameter to specify if the buffer will hold a string
or the binary representation of the value... but that would not help
much (I don't want to deal with internal structures!).

I can manage to bind only basic INTERVAL values for all sort of INTERVAL
columns, no problem...
I did further tests using the "PnnnYnnnM ..." ISO format and that is
working much better.

However I would expect at least 2 classes of INTERVALs to be specified
in libpq parameters:

    INTERVAL YEAR TO MONTH
    INTERVAL DAY TO SECOND(n)

Also: I still have the overflow issue with types like INTERVAL SECOND.
=> discussed in another thread "INTERVAL SECOND limited to 59 seconds?"

Thanks a lot!
Seb

Sam Mason wrote:
> On Tue, May 19, 2009 at 10:08:37AM +0200, Sebastien FLAESCH wrote:
>> I try to use the new 8.4 INTERVAL type with libpq, but get crazy with
>> the input formatting rules...
>
> I think you're giving the database conflicting instructions and it's
> getting confused.
>
>>     fprintf(stdout,"++ Preparing INSERT ...\n");
>>     paramTypes[0] = 23;     /* INT4 */
>>     paramTypes[1] = 1186;   /* INTERVAL */
>>     paramTypes[2] = 1186;   /* INTERVAL */
>
> I don't really know 8.4, but I believe you're saying here that you
> explicitly want the values to be of basic INTERVAL type here, i.e. not
> INTERVAL DAY TO HOUR for parameter 3.
>
> Thus when you do:
>
>>     paramValues[0] = "1";
>>     paramValues[1] = "-12345 years";
>>     paramValues[2] = " 123 11:00";
>>     r = PQexecPrepared(c, "s1", 3, paramValues, NULL, NULL, 0);
>
> It's interpreting " 123 11:00" correctly as a basic INTERVAL value and
> then casting it to your more constrained version as you're saving in the
> table.
>
> However, when you do:
>
>>     paramValues[0] = "2";
>>     paramValues[1] = "-12345";
>>     paramValues[2] = " 123 11";
>>     r = PQexecPrepared(c, "s1", 3, paramValues, NULL, NULL, 0);
>
> You get an error because " 123 11" isn't a valid literal of an
> (undecorated) INTERVAL type.  I think PG may do the right thing if you
> don't specify the types when preparing the query, but haven't tested.
>
>


Re: INTERVAL data type and libpq - what format?

From
Sam Mason
Date:
On Tue, May 19, 2009 at 02:17:20PM +0200, Sebastien FLAESCH wrote:
> As you can do with ODBC's SQLBindParameter(), where you specify the C type,
> SQL type, precision/scale or length ...
> I believe this is important when it comes to data type conversion (for ex,
> when you want to insert a numeric/date/time into a char or the other way).
> => sort of cast, actually...

Tom sent a message, but it seems to have got lost somewhere.  The
suggestion was to leave the paramTypes empty and just write the prepared
statement as:

  INSERT INTO tbl (k,i1,i2) VALUES ($1::INT,$2::INTERVAL YEAR,$3::INTERVAL);

or similar.  That way PG will be able to infer that $1 will be a literal
of integer type, $2 will be of INTERVAL YEAR and so on.  In fact for
queries such as this I don't think you even need to put those casts in
there as PG will be able to figure out what you mean automatically (i.e.
it does a limited form of type inference).

> I known libpq functions like PQexecParams() or PQexecPrepared() have a
> paramFormats[] parameter to specify if the buffer will hold a string
> or the binary representation of the value... but that would not help
> much (I don't want to deal with internal structures!).

Yes, stay away from binary types if at all possible!

> Also: I still have the overflow issue with types like INTERVAL SECOND.
> => discussed in another thread "INTERVAL SECOND limited to 59 seconds?"

If I read it correctly; it's not overflow but truncation.  In PG (I've
got no idea what the database you're comparing to does/what the spec
says it's supposed to do) when you you want a value of type "INTERVAL
SECOND" then PG is interpreting this as meaning I want a value of type
INTERVAL where all the fields apart from the seconds are zero.  Whether
this is useful seems debatable, Richard's suggestion of creating a set
of custom types that do the right thing for you seems like the path of
least resistance.

--
  Sam  http://samason.me.uk/

Re: INTERVAL data type and libpq - what format?

From
Merlin Moncure
Date:
On Tue, May 19, 2009 at 8:17 AM, Sebastien FLAESCH <sf@4js.com> wrote:
> Yes, good point.
>
> I realize now that I would have expected libpq to give me a way to specify
> the exact decoration or precision of INTERVAL parameters...
>
> As you can do with ODBC's SQLBindParameter(), where you specify the C type,
> SQL type, precision/scale or length ...
> I believe this is important when it comes to data type conversion (for ex,
> when you want to insert a numeric/date/time into a char or the other way).
> => sort of cast, actually...
>
> I known libpq functions like PQexecParams() or PQexecPrepared() have a
> paramFormats[] parameter to specify if the buffer will hold a string
> or the binary representation of the value... but that would not help
> much (I don't want to deal with internal structures!).

You might want to take a look at 'libpqtypes'.  It exposes the
internal formats binary formats in easy to use structures.

e.g. (in libpqtypes.h)
typedef struct
{
    int years;
    int mons;
    int days;
    int hours;
    int mins;
    int secs;
    int usecs;
} PGinterval;

I was curious, and decided to see what happens when you inserted an
interval with the following code snippet:

PGinterval i;

memset(&i, 0, sizeof(i));
i.secs = 1000000;

PQputf(p, "%interval", &i);
PQparamExec(c, p, "insert into foo values ($1)", 0);

select * from foo;
     i
-----------
 277:46:40

also, libpqtypes always sends in binary which is much faster for the
date/time types.

http://libpqtypes.esilo.com/

merlin

Re: INTERVAL data type and libpq - what format?

From
Merlin Moncure
Date:
On Tue, May 19, 2009 at 10:12 AM, Sam Mason <sam@samason.me.uk> wrote:
> On Tue, May 19, 2009 at 02:17:20PM +0200, Sebastien FLAESCH wrote:
>> As you can do with ODBC's SQLBindParameter(), where you specify the C type,
>> SQL type, precision/scale or length ...
>> I believe this is important when it comes to data type conversion (for ex,
>> when you want to insert a numeric/date/time into a char or the other way).
>> => sort of cast, actually...
>
> Tom sent a message, but it seems to have got lost somewhere.  The
> suggestion was to leave the paramTypes empty and just write the prepared
> statement as:
>
>  INSERT INTO tbl (k,i1,i2) VALUES ($1::INT,$2::INTERVAL YEAR,$3::INTERVAL);
>
> or similar.  That way PG will be able to infer that $1 will be a literal
> of integer type, $2 will be of INTERVAL YEAR and so on.  In fact for
> queries such as this I don't think you even need to put those casts in
> there as PG will be able to figure out what you mean automatically (i.e.
> it does a limited form of type inference).
>
>> I known libpq functions like PQexecParams() or PQexecPrepared() have a
>> paramFormats[] parameter to specify if the buffer will hold a string
>> or the binary representation of the value... but that would not help
>> much (I don't want to deal with internal structures!).
>
> Yes, stay away from binary types if at all possible!
>

For the record, I disagree with this sentiment strongly.  I would
rather see the client side library be buffed up so you have an
opportunity to deal with the data structures the way the server sees
them.   The more complex the type is (like date time types), the
bigger the win both in terms of performance and feature exposure.  I
understand though that cross database support is impossible though.

merlin

Re: INTERVAL data type and libpq - what format?

From
Sebastien FLAESCH
Date:
Thank you guys for your input, I really appreciate.

It's a while I haven't posted on this list and be happy to get fast and
accurate answers...

As I wrote in a previous mail, I maintain a database driver for our 4GL runtime
system, allowing Informix 4gl applications to use PostgreSQL.

In this context, we have unknown SQL statements to prepare/execute, so it's
note easy to patch the SQL on the fly to add some cast clauses as Sam
suggested...

Regarding your suggestion to use libpqtypes.h:

As a dev tool provider, we cannot force our customers to rely on add-ons
or extensions. Our driver must work with a standard PostgreSQL database.

By the way,

I would also feel more comfortable if the type ids to be passed to the
paramTypes[] array would be provided in a public header file.

I don't understand why this is not published...

Many thanks,
Seb

Merlin Moncure wrote:
> On Tue, May 19, 2009 at 8:17 AM, Sebastien FLAESCH <sf@4js.com> wrote:
>> Yes, good point.
>>
>> I realize now that I would have expected libpq to give me a way to specify
>> the exact decoration or precision of INTERVAL parameters...
>>
>> As you can do with ODBC's SQLBindParameter(), where you specify the C type,
>> SQL type, precision/scale or length ...
>> I believe this is important when it comes to data type conversion (for ex,
>> when you want to insert a numeric/date/time into a char or the other way).
>> => sort of cast, actually...
>>
>> I known libpq functions like PQexecParams() or PQexecPrepared() have a
>> paramFormats[] parameter to specify if the buffer will hold a string
>> or the binary representation of the value... but that would not help
>> much (I don't want to deal with internal structures!).
>
> You might want to take a look at 'libpqtypes'.  It exposes the
> internal formats binary formats in easy to use structures.
>
> e.g. (in libpqtypes.h)
> typedef struct
> {
>     int years;
>     int mons;
>     int days;
>     int hours;
>     int mins;
>     int secs;
>     int usecs;
> } PGinterval;
>
> I was curious, and decided to see what happens when you inserted an
> interval with the following code snippet:
>
> PGinterval i;
>
> memset(&i, 0, sizeof(i));
> i.secs = 1000000;
>
> PQputf(p, "%interval", &i);
> PQparamExec(c, p, "insert into foo values ($1)", 0);
>
> select * from foo;
>      i
> -----------
>  277:46:40
>
> also, libpqtypes always sends in binary which is much faster for the
> date/time types.
>
> http://libpqtypes.esilo.com/
>
> merlin
>


Re: INTERVAL data type and libpq - what format?

From
Tom Lane
Date:
Sam Mason <sam@samason.me.uk> writes:
> I don't really know 8.4, but I believe you're saying here that you
> explicitly want the values to be of basic INTERVAL type here, i.e. not
> INTERVAL DAY TO HOUR for parameter 3.

Right, you can get the equivalent behavior from psql thus:

regression=# select '-12345'::interval::interval year;
 interval
----------
 00:00:00
(1 row)

regression=# select '12 11'::interval::interval year;
ERROR:  invalid input syntax for type interval: "12 11"
LINE 1: select '12 11'::interval::interval year;
               ^

There is not any way to bind a more specific type to a parameter at the
protocol level.

> I think PG may do the right thing if you
> don't specify the types when preparing the query, but haven't tested.

Yeah, that should work (though I haven't verified it either).  Another
common trick is to specify the type in the text of the query by casting
the parameter symbol:

    PQprepare( ... $2::INTERVAL YEAR ... );

I'd say this is better style than hard-wiring numeric type OIDs into
your code.

            regards, tom lane

Re: INTERVAL data type and libpq - what format?

From
Sebastien FLAESCH
Date:
Tom Lane wrote:
> Sam Mason <sam@samason.me.uk> writes:
>> I don't really know 8.4, but I believe you're saying here that you
>> explicitly want the values to be of basic INTERVAL type here, i.e. not
>> INTERVAL DAY TO HOUR for parameter 3.
>
> Right, you can get the equivalent behavior from psql thus:
>
> regression=# select '-12345'::interval::interval year;
>  interval
> ----------
>  00:00:00
> (1 row)
>
> regression=# select '12 11'::interval::interval year;
> ERROR:  invalid input syntax for type interval: "12 11"
> LINE 1: select '12 11'::interval::interval year;
>                ^
>
> There is not any way to bind a more specific type to a parameter at the
> protocol level.
>
>> I think PG may do the right thing if you
>> don't specify the types when preparing the query, but haven't tested.
>
> Yeah, that should work (though I haven't verified it either).  Another
> common trick is to specify the type in the text of the query by casting
> the parameter symbol:
>
>     PQprepare( ... $2::INTERVAL YEAR ... );
>
> I'd say this is better style than hard-wiring numeric type OIDs into
> your code.

Remember we are implementing a database driver with equivalent features
and an ODBC driver for PostgreSQL, executing queries with ? parameter
placeholders in the SQL text...

Since SQL Parameter types are not known at (4gl language-level) PREPARE
time, we wait for the (4gl) EXECUTE time to do the real PQprepare() with
paramTypes[]... (this is a pity by the way since we can't get any SQL
error at PREPARE time).

It's not that easy for us to add the ::<type> clauses because the conversion
of the ? placeholders to $n is done at PREPARE time, when types are not yet
yet... so this means major rewriting...

But this is all internal stuff you are not interested in, the main question
I would like to ask is:

What versions of PostgreSQL are 100% sure supporting the $n::<type> clauses?

We have to support all PostgreSQL versions, starting from 8.0 ...

Thanks
Seb

Re: INTERVAL data type and libpq - what format?

From
Ron Mayer
Date:
Sam Mason wrote:
> You get an error because " 123 11" isn't a valid literal of an
> (undecorated) INTERVAL type.

Hmm..... should it be?
Skimming the spec makes me think it might be a valid day-time interval.

Quoting the spec:
<unquoted interval string> ::=
  [ <sign> ] { <year-month literal> | <day-time literal> }
...
<day-time literal> ::=
    <day-time interval>
  | <time interval>
<day-time interval> ::=
  <days value> [ <space> <hours value> [ <colon> <minutes value>
      [ <colon> <seconds value> ] ] ]

I can send a patch if this interpretation is right...



Re: INTERVAL data type and libpq - what format?

From
Tom Lane
Date:
Ron Mayer <rm_pg@cheapcomplexdevices.com> writes:
> Sam Mason wrote:
>> You get an error because " 123 11" isn't a valid literal of an
>> (undecorated) INTERVAL type.

> Hmm..... should it be?

Well, we do allow it if it's *explicitly* stated to be a day to hour
interval:

regression=# select interval '123 11' day to hour;
     interval
-------------------
 123 days 11:00:00
(1 row)

What's at issue here is what should happen without that context.
I'm inclined to think this is ambiguous enough that accepting it
silently isn't such a great idea.  I'm also not convinced that the
SQL spec says we must --- the syntax for <interval literal> does
not appear to allow omitting the fields specification.

In a related example,

regression=# select interval '123 11' day;
 interval
----------
 134 days
(1 row)

we seem to be adding the 123 and 11 together.  This is, um,
surprising behavior ... I'd be inclined to think throwing an
error is more appropriate.

            regards, tom lane

Re: INTERVAL data type and libpq - what format?

From
Sebastien FLAESCH
Date:
Tom Lane wrote:
> Ron Mayer <rm_pg@cheapcomplexdevices.com> writes:
>> Sam Mason wrote:
>>> You get an error because " 123 11" isn't a valid literal of an
>>> (undecorated) INTERVAL type.
>
>> Hmm..... should it be?
>
> Well, we do allow it if it's *explicitly* stated to be a day to hour
> interval:
>
> regression=# select interval '123 11' day to hour;
>      interval
> -------------------
>  123 days 11:00:00
> (1 row)
>
> What's at issue here is what should happen without that context.
> I'm inclined to think this is ambiguous enough that accepting it
> silently isn't such a great idea.  I'm also not convinced that the
> SQL spec says we must --- the syntax for <interval literal> does
> not appear to allow omitting the fields specification.
>
> In a related example,
>
> regression=# select interval '123 11' day;
>  interval
> ----------
>  134 days
> (1 row)
>
> we seem to be adding the 123 and 11 together.  This is, um,
> surprising behavior ... I'd be inclined to think throwing an
> error is more appropriate.
>
>             regards, tom lane
>

FYI, with Informix, you don't pass a string in interval literals;
You pass digits directly and the interval qualifiers are mandatory:

=======================================================================

 > select interval('123 11') from systables where tabid=1;

   201: A syntax error has occurred.
Error in line 1
Near character position 27


 > select interval(123 11) from systables where tabid=1;

   201: A syntax error has occurred.
Error in line 1
Near character position 25


 > select interval(123 11) day to hour from systables where tabid=1;

  1261: Too many digits in the first field of datetime or interval.
Error in line 1
Near character position 37


 > select interval(123 11) day(3) to hour from systables where tabid=1;


(constant)

  123 11

1 row(s) retrieved.

=======================================================================

Seb