2

My application uses slick (v2.0.0) to manage a postgresql (9.1) database.

One of my tables contains network devices and therefore mac- and ip-addresses. I used the postgres types macaddr and inet as they seemed like the perfect tool for the task. Now I want to use slick with lifted emebedding but struggle to define my tables. When I auto generated the code for my tables I noticed that String was used instead of those types but didn't mind.

This works fine for reading from the DB but when I try to update or insert a row it causes

org.postgresql.util.PSQLException: ERROR: 
column "mac" is of type macaddr but expression is of type character varying
Hint: You will need to rewrite or cast the expression.

wich seems pretty logical.

Now the question is how can I tell slick that:

  • This collumn is actually of type macaddr/inet

or

  • This collumn needs to be casted before insertion but can otherwise be treated as a string

?

Update:

As Craig described I have created implicit casts using wrapper functions around macaddr_in and inet_in

\dC macaddr
                 List of casts
 Source type | Target type |    Function    | Implicit?
-------------+-------------+----------------+-----------
 text        | macaddr     | macaddr_intext | yes

\dC inet
                        List of casts
 Source type |    Target type    |      Function      |   Implicit?
-------------+-------------------+--------------------+---------------
 cidr        | inet              | (binary coercible) | yes
 inet        | character         | text               | in assignment
 inet        | character varying | text               | in assignment
 inet        | cidr              | cidr               | in assignment
 inet        | text              | text               | in assignment
 text        | inet              | inet_intext        | yes

\df+ macaddr_intext
                                                                  List of functions
 Schema |      Name      | Result data type | Argument data types |  Type  | Volatility |  Owner   | Language |           Source code           | Description
--------+----------------+------------------+---------------------+--------+------------+----------+----------+---------------------------------+-------------
 public | macaddr_intext | macaddr          | text                | normal | immutable  | postgres | sql      |                                 |
                                                                                                              : select macaddr_in($1::cstring);
                                                                                                              :

\df+ inet_intext
                                                               List of functions
 Schema |    Name     | Result data type | Argument data types |  Type  | Volatility |  Owner   | Language |         Source code          | Description
--------+-------------+------------------+---------------------+--------+------------+----------+----------+------------------------------+-------------
 public | inet_intext | inet             | text                | normal | immutable  | postgres | sql      |                              |
                                                                                                           : select inet_in($1::cstring);
                                                                                                           :

Errormessage is still exactly the same as shown above.

Commads to reproduce:

in psql <tablename>:

create or replace function macaddr_intext(text) returns macaddr as $$
select macaddr_in($1::cstring);
$$ language sql immutable;

create cast (text as macaddr) with function macaddr_intext(text) as implicit;

create or replace function inet_intext(text) returns inet as $$
select inet_in($1::cstring);
$$ language sql immutable;

create cast (text as inet) with function inet_intext(text) as implicit;

Update2:

I narrowed it down to a permission error because if I run as user postgres:

mydb=# create table test(i inet, m macaddr)
CREATE TABLE
mydb=# insert into test values ('1.1.1.1'::text, '00:00:00:00:00:00'::text);
INSERT 0 1

but if I try to run it as the user that actually tries to insert

mydb=> insert into test values ('1.1.1.1'::text, '00:00:00:00:00:00'::text);
ERROR: permission denied for relation test

when setting up the database I had run:

template1=# GRANT ALL PRIVILEGES ON DATABASE mydb to myuser;

Update3:

Update2 turned out to be only the problem because the created table was owned by postgres instead of myuser

mgttlinger
  • 1,435
  • 2
  • 21
  • 35

1 Answers1

6

This is a variant of the problem many people have with json or XML and comes down to the fact that PostgreSQL is way too strict about casts between data types.

See this answer which discusses a similar issue with json. The same approach, of creating a cast using the conversion function, is appropriate here.

Your cast functions are macaddr_in and inet_in. You'll need to write wrapper SQL functions that take text arguments because of some irritating type issues. See the above link.

See also this related answer for the xml type.


After your update I tested your functions and found them to work as expected:

postgres=# CREATE TABLE inetmac (i inet, m macaddr);
CREATE TABLE
postgres=# PREPARE insinet(text) AS INSERT INTO inetmac(i) VALUES ($1);
PREPARE
postgres=# EXECUTE insinet('10.1.1.1');
INSERT 0 1
postgres=# 

... but, rather surprisingly, Pg doesn't implicitly cast from varchar to text in order to use the text casts:

postgres=# PREPARE insinet(varchar) AS INSERT INTO inetmac(i) VALUES ($1);
ERROR:  column "i" is of type inet but expression is of type character varying
LINE 1: ...PARE insinet(varchar) AS INSERT INTO inetmac(i) VALUES ($1);
                                                                   ^
HINT:  You will need to rewrite or cast the expression.

If you're using varchar input you'll need another set of casts with varchar instead of text input.

Note that this won't happen if you just:

INSERT INTO inetmac(i) VALUES ('10.1.1.1');

directly, because here '10.1.1.1 is of pseudo-type unknown, and gets interpreted as inet since that's what the input requires. It's kind of like a type that's implicitly castable to any input function. By contrast text and varchar are concrete types that Pg must consult the casting rules for, so without creating casts, this won't work:

INSERT INTO inetmac(i) VALUES ('10.1.1.1'::text);

I'm going to raise a fuss about this on the -hackers list, it's just painful how difficult it is for people to use PostgreSQL's extension types from client interfaces. Sure, said client interfaces should expose a way to tell the JDBC driver what the base type is, but we're lucky if most things cope with the extreme basics like indexes and composite keys, let alone details of type handling. Pg really needs to be a bit less nuts about this, or provide a way to specify the 'unknown' pseudo-type in parameter binding.

Fuss raised:

Community
  • 1
  • 1
Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
  • I have created the two wrapper functions `macaddr_intext(text)` and `inet_intext(text)` like you described in your linked post and also the implicit casts using those functions. But I still get exactly the same error when I try to insert my `macaddr` as `text`. – mgttlinger Jan 28 '14 at 06:41
  • @mgttlinger Should work; edit the question with exact function defs and commands run, new error (if changed)? – Craig Ringer Jan 28 '14 at 06:50
  • Added function and cast defs to the question. Does it matter that the owner of the functions is different than the user that tries to insert? – mgttlinger Jan 28 '14 at 06:59
  • @mgttlinger The `CREATE` commands would be good - for one thing, it'd help others who find this later. For another, it helps me actually reproduce what you're doing to see what might be going wrong. And no, the owner doesn't matter so long as the role(s) have execute rights. – Craig Ringer Jan 28 '14 at 07:04
  • Added those as well. I have `GRANT`ed the user `ALL PRIVILEGES` on the database. – mgttlinger Jan 28 '14 at 07:14
  • @mgttlinger Per edit, Pg isn't finding a cast chain from varchar to text, then text to inet. Will need another set of casts for `varchar` despite them being basically identical everywhere else in Pg. – Craig Ringer Jan 28 '14 at 07:31
  • I think I don't fully understand the problem. Using `psql` it works but using jdbc postgres doens't get that it has to cast `varchar` to `text` and then to `macaddr`? – mgttlinger Jan 28 '14 at 07:44
  • @mgttlinger To simulate this in `psql` you need to use `PREPARE` then `EXECUTE`, or explicitly cast your literals to `varchar`. The problem is with type-binding of parameters; normally `'10.1.1.1'` is of pseudo-type `unknown`, which triggers type inference during planning. If it's a concrete type like `'10.1.1.1'::varchar` instead, Pg consults the casting rules. See the edit abve. – Craig Ringer Jan 28 '14 at 07:46
  • Ahh OK now I get it. Thank you very much for your patience and help. – mgttlinger Jan 28 '14 at 07:48
  • @mgttlinger You're welcome. Please do me a favour and complain about this on the pgsql-general mailing list. Politely, but explaining why this strictness about casting of inputs is such a painpoint for people using wrappers around PgJDBC. – Craig Ringer Jan 28 '14 at 07:49
  • OK I have written an email which is waiting for approval by the moderators right now. – mgttlinger Jan 28 '14 at 20:14
  • @mgttlinger For future readers, the thread begins with: http://www.postgresql.org/message-id/CACTajFZ8+hg_kom6QiVBa94Kx9L3XUqZ99RdUsHBFkSb1MoCPQ@mail.gmail.com . If you're not subscribed to the list you can also see replies that didn't Cc you on the archives for that thread. – Craig Ringer Jan 29 '14 at 04:10