4

This is a follow-up question to: How to cast to int array in PostgreSQL?
I am thinking how to convert Python's datatype of array-array of signed integer into to int of PostgreSQL quickly:

import numpy as np; # use any data format of Python here
event = np.array([[1,2],[3,4]]);

where [] should be replaced by {} and surrounded by ' if manually. In PostgreSQL, the following is accepted as the syntax of the datatype

...
FOR EACH ROW EXECUTE PROCEDURE insaft_function('{{1,2},{3,4}}'); 

@JohnMee's suggestion

str(event).replace('[','{').replace(']','}').replace('\n ',',')

@ErwinBrandstetter's suggestion

Stick to signed integers because it is supported by SQL standard. Map to int, so just in PostgreSQL side:

TG_ARGV::int[]

I want to stick to this Erwin's suggestion.

Test run of simpler version of @ErwinBrandstetter's answer

I have to simplify his answer to keep it enough focused here by removing the table-name from function so just keeping the trigger for one initial table measurements:

CREATE OR REPLACE FUNCTION f_create_my_trigger(_arg0 text)
  RETURNS void AS
$func$
BEGIN
EXECUTE format($$
    DROP TRIGGER IF EXISTS insaft_ids ON measurements;
    CREATE TRIGGER insaft_ids
    AFTER INSERT ON measurements
    FOR EACH ROW EXECUTE PROCEDURE insaft_function(%1$L)$$
    , _arg0
);

END
$func$ LANGUAGE plpgsql;

And I run:

sudo -u postgres psql detector -c "SELECT f_create_my_trigger('[[1,2],[3,4]]');"

But get empty output:

 f_create_my_trigger 
---------------------

(1 row)

How can you map to int for PostgreSQL 9.4 in Python?

Community
  • 1
  • 1
Léo Léopold Hertz 준영
  • 134,464
  • 179
  • 445
  • 697
  • 2
    presumably a follow up to [this question](http://dba.stackexchange.com/questions/107079/how-to-cast-to-int-array-in-postgresql) – John Mee Jul 27 '15 at 11:32
  • Do you just want the array massaged into that string format, or something more? – John Mee Jul 27 '15 at 11:39
  • Presumably something more, can you clarify what you're having trouble with, tried, etc? Because the string format is trivial... `str(event).replace('[','{').replace(']','}').replace('\n ',',')` – John Mee Jul 27 '15 at 11:45
  • 2
    Careful with the data type **`unsigned integer`**. Standard Postgres only supports signed integer as defined in the SQL standard. You may want to map to `bigint` instead of `int` to avoid an overflow in the upper half. Or install the extension [**`pguint`** provided by Peter Eisentraut](https://github.com/petere/pguint). – Erwin Brandstetter Jul 27 '15 at 15:21
  • @ErwinBrandstetter Excellent comment! Then, I want to stick only to signed integer. How can you do this mapping from `bigint` to `int`? – Léo Léopold Hertz 준영 Jul 27 '15 at 16:05
  • 1
    The array literal stays the same, that's just a text representation. But cast to `bigint[]`, not `int[]` on the Postgres side. In [my previous answer](http://dba.stackexchange.com/a/107086/3684) that would be `TG_ARGV[0]::bigint[]`. I can't comment on the NumPy part, since I don't use it. – Erwin Brandstetter Jul 27 '15 at 17:44
  • @ErwinBrandstetter We can skip Numpy. I trust that you know the topic much better than me. How would you do the conversion from Python dataformat to PostgreSQL `bigint[]`? – Léo Léopold Hertz 준영 Jul 27 '15 at 18:05
  • 1
    Now that you've change your question to "signed integer", you can map to plain `integer` in Postgres. I am not sure how to understand the updated question: `How can you map from bigint to int for the example of PostgreSQL 9.4 in Python?` I assume you have a 2-dimenstional array of (signed?) integer numbers in Python? Just make sure that numbers don't overflow. [Here are the specs for the discussed integer types in Postgres.](http://www.postgresql.org/docs/devel/static/datatype-numeric.html) – Erwin Brandstetter Jul 28 '15 at 03:38
  • @ErwinBrandstetter Thank you! My misunderstanding. So just keep in plain `integer` in Postgres. Assume 2-dimensional array of signed int. – Léo Léopold Hertz 준영 Jul 28 '15 at 06:18

1 Answers1

2

Setup

You want to create triggers (repeatedly?) using the same trigger function like outlined in my related answer on dba.SE. You need to pass values to the trigger function to create multiple rows with multiple column values, hence the two-dimensional array. (But we can work with any clearly defined string!)

The only way to pass values to a PL/pgSQL trigger function (other than column values of the triggering row) are text parameters, which are accessible inside the function as 0-based array of text in the special array variable TG_ARGV[]. You can pass a variable number of parameters, but we discussed a single string literal representing your 2-dimenstional array earlier.

Input comes from a 2-dimensional Python array with signed integer numbers, that fits into the Postgres type integer. Use the Postgres type bigint to cover unsigned integer numbers, as commented.

The text representation in Python looks like this:

[[1,2],[3,4]]

Syntax for a Postgres array literal:

{{1,2},{3,4}}

And you want to automate the process.

Full automation

You can concatenate the string for the CREATE TRIGGER statement in your client or you can persist the logic in a server-side function and just pass parameters.

Demonstrating an example function taking a table name and the string that's passed to the trigger function. The trigger function insaft_function() is defined in your previous question on dba.SE.

CREATE OR REPLACE FUNCTION f_create_my_trigger(_tbl regclass, _arg0 text)
  RETURNS void
  LANGUAGE plpgsql AS
$func$
BEGIN
   EXECUTE format($$
      DROP TRIGGER IF EXISTS insaft_%1$s_ids ON %1$s;
      CREATE TRIGGER insaft_%1$s_ids
      AFTER INSERT ON %1$s
      FOR EACH ROW EXECUTE PROCEDURE insaft_function(%2$L)$$
                , _tbl
                , translate(_arg0, '[]', '{}')
      );
END 
$func$;

Call:

SELECT f_create_my_trigger('measurements', '[[1,2],[3,4]]');

Or:

SELECT f_create_my_trigger('some_other_table', '{{5,6},{7,8}}');

db<>fiddle here
Old sqlfiddle

Now you can pass either [[1,2],[3,4]] (with square brackets) or {{1,2},{3,4}} (with curly braces). Both work the same. translate(_arg0, '[]', '{}' transforms the first into the second form.

This function drops a trigger of the same name if it exists, before creating the new one. You may want to drop or keep this line:

DROP TRIGGER IF EXISTS insaft_%1$s_ids ON %1$s;

This runs with the privileges of the calling DB role. You could make it run with superuser (or any other) privileges if need be. See:

There are many ways to achieve this. It depends on exact requirements.

Explaining format()

format() and the data type regclass help to safely concatenate the DDL command and make SQL injection impossible. See:

The first argument is the "format string", followed by arguments to be embedded in the string. I use dollar-quoting, which is not strictly necessary for the example, but generally a good idea for concatenating long strings containing single-quotes: $$DROP TRIGGER ... $$

format() is modeled along the C function sprintf. %1$s is a format specifier of the format() function. It means that the first (1$) argument after the format string is inserted as unquoted string (%s), hence: %1$s. The first argument to format is _tbl in the example - the regclass parameter is rendered as legal identifier automatically, double-quoted if necessary, so format() does not have to do more. Hence just %s, not %I (identifier). Read the linked answer above for details.
The other format specifier in use is %2$L: Second argument as quoted string literal.

If you are new to format(), play with these simple examples to understand:

SELECT format('input -->|%s|<-- here', '[1,2]')
     , format('input -->|%s|<-- here', translate('[1,2]', '[]', '{}'))
     , format('input -->|%L|<-- here', translate('[1,2]', '[]', '{}'))
     , format('input -->|%I|<-- here', translate('[1,2]', '[]', '{}'));

And read the manual.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • I included my test run in the body of the question. I simplified it to one single table, since having some trouble with this `_%s$s`. However, I also included now in the test run three more indexes in parameters. I do not know if this is wise. Or should everything be just a string? Now having _arg0 to _arg3. – Léo Léopold Hertz 준영 Jul 28 '15 at 08:56
  • 1
    @Masi: You can pass as many parameters as you like as long as it works for your trigger function. But don't confuse references to function parameters with the "format specifier" of `format()`. I added a chapter to explain. *Follow the links for details.* – Erwin Brandstetter Jul 28 '15 at 12:56
  • Can you please explain the following part more? `format($$ ... FOR EACH ROW EXECUTE PROCEDURE insaft_function(%2$L)$$, _tbl, translate(_arg0, '[]', '{}') );` You apply `format` first to the table name. Earlier I had `FOR EACH ROW EXECUTE PROCEDURE insaft_function(1, 2, 3, '{{1,2},{3,4}}'); ` where the function has 4 parameters. In your example, there seems to be only one parameter with `insaft_function`. `translate` is apparently run after the function. – Léo Léopold Hertz 준영 Jul 29 '15 at 10:38
  • I made a minimum example of your code in the body of question where I get an empty output. – Léo Léopold Hertz 준영 Jul 29 '15 at 11:15
  • 1
    @Masi: `translate()` is applied on the input parameter `_arg0` *before* feeding the result of this expression as parameter to `format()`. The function `f_create_my_trigger()` is *expected* to return nothing, it is defined with `RETURNS void`. It (re-)creates a *trigger* on table `measurements` that inserts rows in table `event` after every insert to measurements. You need to understand what functions, triggers and trigger functions are before you proceed with this complex task. Are you even sure you need a trigger? – Erwin Brandstetter Jul 29 '15 at 12:49
  • Returning thing can be better. Still revieving it to the trigger implementation. – Léo Léopold Hertz 준영 Jul 29 '15 at 12:52
  • Thank you for your answer! I have done several tests based on your answer. I gave you full credits because otherwise this thread would expand a lot easily. I instead opened a new thread with better focus on BEFORE INSERT here http://stackoverflow.com/q/31774157/54964 where the chain of events is less. – Léo Léopold Hertz 준영 Aug 02 '15 at 16:50