Today, we’re pleased to announce support for PostgreSQL 15. 

PostgreSQL 15 includes improved sort performance, write-ahead log (WAL) file compression, and other improvements. Here are some features you can try using Neon’s SQL Editor.

SQL MERGE

The MERGE query can UPDATE, INSERT, or DELETE rows with a single SQL statement. PostgreSQL will insert a new row if it doesn’t exist, otherwise, it will UPDATE, DELETE, or DO NOTHING as specified in the query clause. 

PostgreSQL supports the INSERT ON CONFLICT statement with similar functionality since version 9.5. INSERT ON CONFLICT and MERGE handle concurrent insertions differently. INSERT ON CONFLICT DO UPDATE guarantees either INSERT or UPDATE outcome, while MERGE may fail with a unique violation. 

Thus, it is preferred to run UPDATE in the case of concurrent INSERT statements.

INSERT INTO table_name(column_list) 
VALUES(value_list)
ON CONFLICT target action;

Let’s see MERGE in action to understand how it works.

Using MERGE in Neon

We have created a hero table with the following attributes:

CREATE TABLE hero (
    "id" int4,
    "first_name" text,
    "last_name" text,
    "hero_name" text,
    PRIMARY KEY ("id")
);

Since the table is empty, the following query will not find a match. As a result, the query will insert a row with id=11.

MERGE INTO hero c
USING (VALUES(11, 'Bruce', 'Wayne', 'Batman')) v
ON v.column1 = c.id
WHEN MATCHED THEN
  UPDATE SET last_name = v.column3, hero_name = v,column4
WHEN NOT MATCHED THEN
  INSERT (id, first_name, last_name, email)
  VALUES (v.column1, v.column2, v.column3, v.column4);

If we run the same query a second time, PostgreSQL will find a match with the same value as before, so it will do nothing.

Let’s make a few changes to the query shown above. We kept the same id=11 but changed the last_name and hero_name columns’ respective values. Can you guess what happens when we execute the following query? 

MERGE INTO hero c
USING (VALUES(11, 'Bruce', 'Banner', 'The Hulk')) v
ON v.column1 = c.id
WHEN MATCHED THEN
  UPDATE SET last_name = v.column3, hero_name = v,column4
WHEN NOT MATCHED THEN
  INSERT (id, first_name, last_name, email)
  VALUES (v.column1, v.column2, v.column3, v.column4);

You got it! PostgreSQL updates the last_name and hero_name columns with the values Banner and The Hulk.

CREATE privilege on public schema removed

PostgreSQL 15 removes the global write privilege from the public schema to limit possible security issues. For example, if you have a database owned by the user alice and connect to it from the user bob, you will see ERROR: permission denied for schema public when running this query:

CREATE TABLE bob_table (
    id SERIAL PRIMARY KEY,
    data TEXT
);

To enable this functionality, connect from the database owner and run the following statement:

GRANT CREATE ON SCHEMA public TO bob; 

Only grant these permissions to trusted users, as those can be exploited. Refer to the Guide to CVE-2018-1058  for more information about protecting your search path.

Allow unique constraints and indexes to treat NULL values as not distinct

In previous PostgreSQL versions, you could insert multiple rows with a NULL attribute, even if the column is specified as UNIQUE

Using UNIQUE NULLS NOT DISTINCT

Let’s look at an example. Create a table called superhero with a unique hero_name attribute.

CREATE TABLE superhero (
    id SERIAL PRIMARY KEY,
    hero_name TEXT,
    UNIQUE (hero_name)
);

Superheroes are known to keep their identity secret, so let’s insert their data into the database with the hero_name attribute set to NULL. Use the following INSERT queries to keep the superheroes’ names anonymous.

INSERT INTO superhero (hero_name) VALUES(NULL);
INSERT INTO superhero (hero_name) VALUES(NULL);

Superpowers, on the other hand, have to be specified. I can only allow one type of superpower to be NULL. With the change to PostgreSQL 15, we can ensure that only one NULL in the superpower_name column is permitted:

CREATE TABLE superpower (
    id SERIAL PRIMARY KEY,
    superpower_name TEXT,
    UNIQUE NULLS NOT DISTINCT (superpower_name)
);

If I try to run the following SQL statement a second time, I’ll get Error: duplicate key value violates unique constraint.

INSERT INTO superpower (superpower_name) VALUES(NULL);

Try out Postgres 15

Get started today at https://neon.tech and create a PostgresSQL 15 project.

Create a PostgreSQL 15 project on Neon

Do you want to migrate your current project to PostgreSQL 15 with Neon? Follow the instructions in import data from PostgreSQL.

Let us know what other features you liked in this release. Share your ideas at https://community.neon.tech/