Files
windmill/backend/migrations/20220123221903_first.up.sql
Ruben Fiszel 2e132878e4 first commit
2022-05-05 04:25:58 +02:00

576 lines
20 KiB
SQL

-- Add migration script here
create SCHEMA IF NOT exists extensions;
create extension if not exists "uuid-ossp" with schema extensions;
CREATE TABLE workspace (
id VARCHAR(50) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
owner VARCHAR(50) NOT NULL,
domain VARCHAR(30),
deleted BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT proper_id CHECK (id ~ '^\w+(-\w+)*$')
);
INSERT INTO workspace(id, name, owner) VALUES
('starter', 'Starter', 'admin@windmill.dev'),
('demo', 'Demo', 'admin@windmill.dev');
CREATE TABLE script (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
hash BIGINT NOT NULL,
path varchar(255) NOT NULL,
parent_hashes BIGINT[],
summary TEXT NOT NULL,
description TEXT NOT NULL,
content TEXT NOT NULL,
created_by VARCHAR(50) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
archived BOOLEAN NOT NULL DEFAULT false,
schema JSONB,
deleted BOOLEAN NOT NULL DEFAULT false,
is_template boolean DEFAULT false,
PRIMARY KEY (workspace_id, hash),
CONSTRAINT proper_id CHECK (path ~ '^[ug](\/[\w-]+){2,}$')
);
CREATE TABLE flow (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
path varchar(255) NOT NULL,
summary TEXT NOT NULL,
description TEXT NOT NULL,
value JSONB NOT NULL,
edited_by VARCHAR(50) NOT NULL,
edited_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
archived BOOLEAN NOT NULL DEFAULT false,
schema JSONB,
PRIMARY KEY (workspace_id, path),
CONSTRAINT proper_id CHECK (path ~ '^[ug](\/[\w-]+){2,}$')
);
INSERT INTO script(workspace_id, created_by, content, schema, summary, description, path, hash) VALUES (
'starter',
'system', 'import wmill
import psycopg2
client = wmill.Client()
def main():
# query that returns rows will return them as a list
res1 = query_pg("SELECT * from demo", "g/all/demodb")
# query that does not return rows will return None
res2 = query_pg("UPDATE demo SET value = ''value''", "g/all/demodb")
# one can use RETURNING to still fetch the updated rows
res3 = query_pg("UPDATE demo SET value = ''value'' RETURNING *", "g/all/demodb")
# output expects a dict
return {"res1": res1, "res2": res2, "res3": res3}
def query_pg(query: str, resource: str):
pg_con = client.get_resource(resource)
conn = psycopg2.connect(**pg_con)
cur = conn.cursor()
cur.execute(f"{query};")
if cur.description:
return cur.fetchall()
else:
return None',
'{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {},
"required": [],
"type": "object"
}',
'Query the demodb resource','
An example of how to use resources from scripts. In this example, we will query the demo database demodb that is set up by default on Windmill.',
'u/bot/postgres_example', 43),
(
'starter',
'system',
'import os
def main(name: str = "Nicolas Bourbaki"):
print(f"Hello World and a warm welcome especially to {name}")
print("The env variable at `g/all/pretty_secret`: ", os.environ.get("G_ALL_PRETTY_SECRET"))
return {"len": len(name), "splitted": name.split() }',
'{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"name": {
"description": "",
"type": "string",
"default": "Nicolas Bourbaki"
}
},
"required": [],
"type": "object"
}',
'Hello World', '', 'u/bot/hello_world', 44);
CREATE INDEX index_script_on_path_created_at ON script (path, created_at);
CREATE TYPE JOB_KIND AS ENUM ('script', 'preview', 'flow', 'dependencies');
CREATE TABLE queue (
id UUID PRIMARY KEY,
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
parent_job UUID,
created_by VARCHAR(50) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
started_at TIMESTAMP WITH TIME ZONE,
scheduled_for TIMESTAMP WITH TIME ZONE NOT NULL,
running BOOLEAN NOT NULL DEFAULT FALSE,
script_hash BIGINT,
script_path VARCHAR(255),
args JSONB,
logs TEXT,
raw_code TEXT,
canceled boolean NOT NULL DEFAULT false,
canceled_by VARCHAR(50),
canceled_reason TEXT,
last_ping TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
job_kind JOB_KIND NOT NULL DEFAULT 'script',
env_id INTEGER,
schedule_path VARCHAR(255),
permissioned_as VARCHAR(55) NOT NULL DEFAULT 'g/all',
flow_status JSONB
);
CREATE INDEX index_queue_on_workspace_id ON queue (workspace_id);
CREATE INDEX index_queue_on_scheduled_for ON queue (scheduled_for);
CREATE INDEX index_queue_on_running ON queue (running);
CREATE INDEX index_queue_on_created ON queue (created_at);
CREATE INDEX index_queue_on_script_path ON queue (script_path);
CREATE INDEX index_queue_on_script_hash ON queue (script_hash);
CREATE TABLE completed_job (
id UUID PRIMARY KEY,
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
parent_job UUID,
created_by VARCHAR(50) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
duration INT NOT NULL,
success BOOLEAN NOT NULL,
script_hash BIGINT,
script_path VARCHAR(255),
args JSONB,
result JSONB,
logs TEXT,
deleted BOOLEAN NOT NULL DEFAULT false,
raw_code TEXT,
canceled boolean NOT NULL DEFAULT false,
canceled_by VARCHAR(50),
canceled_reason TEXT,
job_kind JOB_KIND NOT NULL DEFAULT 'script',
env_id INTEGER NOT NULL DEFAULT 0,
schedule_path varchar(255),
permissioned_as VARCHAR(55) NOT NULL DEFAULT 'g/all',
flow_status JSONB
);
CREATE INDEX index_completed_on_workspace_id ON completed_job (workspace_id);
CREATE INDEX index_completed_on_created ON completed_job (created_at);
CREATE INDEX index_completed_on_script_path ON completed_job (script_path);
CREATE INDEX index_completed_on_script_hash ON completed_job (script_hash);
CREATE TABLE usr (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
username VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL,
is_admin BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
operator BOOLEAN NOT NULL DEFAULT false,
disabled BOOLEAN NOT NULL DEFAULT false,
role VARCHAR(50),
PRIMARY KEY (workspace_id, username),
CONSTRAINT proper_username CHECK (username ~ '^[\w-]+$'),
CONSTRAINT proper_email CHECK (email ~ '^(?:[a-z0-9!#$%&''*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$')
);
CREATE INDEX index_usr_email ON usr (email);
CREATE TYPE LOGIN_TYPE AS ENUM ('password', 'github');
CREATE TABLE password (
email VARCHAR(50) PRIMARY KEY,
password_hash VARCHAR(100),
login_type LOGIN_TYPE NOT NULL,
super_admin BOOLEAN NOT NULL DEFAULT FALSE,
verified BOOLEAN NOT NULL DEFAULT FALSE,
name VARCHAR(30),
company VARCHAR(30)
);
-- CREATE TABLE invite_code (
-- code VARCHAR(20) PRIMARY KEY,
-- seats_left INTEGER NOT NULL DEFAULT 0,
-- seats_given INTEGER NOT NULL DEFAULT 1
-- );
CREATE TABLE workspace_settings (
workspace_id VARCHAR(50) PRIMARY KEY REFERENCES workspace(id),
slack_team_id VARCHAR(50) UNIQUE,
slack_name VARCHAR(50),
slack_command_script VARCHAR(255)
);
INSERT INTO workspace_settings (workspace_id) VALUES
('starter'),
('demo');
CREATE TABLE workspace_invite (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
email VARCHAR(50),
is_admin bool NOT NULL DEFAULT false,
CONSTRAINT proper_email CHECK (email ~ '^(?:[a-z0-9!#$%&''*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$'),
PRIMARY KEY (workspace_id, email)
);
CREATE TABLE magic_link (
email VARCHAR(50) NOT NULL,
token VARCHAR(100) NOT NULL,
expiration TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (NOW() + interval '1 day'),
PRIMARY KEY (email, token)
);
CREATE TABLE token (
token VARCHAR(50) PRIMARY KEY,
label VARCHAR(50),
expiration TIMESTAMP WITH TIME ZONE,
workspace_id VARCHAR(50) REFERENCES workspace(id),
owner VARCHAR(55),
email VARCHAR(50),
super_admin BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
last_used_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE INDEX index_magic_link_exp ON magic_link (expiration);
CREATE INDEX index_token_exp ON token (expiration);
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
('starter', 'admin@windmill.dev', 'admin', true, 'Admin'),
('demo', 'admin@windmill.dev', 'admin', true, 'Ruben');
INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company) VALUES
('admin@windmill.dev', true, '$argon2id$v=19$m=4096,t=3,p=1$z0Kg3qyaS14e+YHeihkJLQ$N69flI6yQ/U98pjAHtbNxbdz2f4PrJEi9Tx1VoYk1as', 'password', true, 'Admin', 'Windmill'),
('ruben@windmill.dev', true, '$argon2id$v=19$m=4096,t=3,p=1$z0Kg3qyaS14e+YHeihkJLQ$N69flI6yQ/U98pjAHtbNxbdz2f4PrJEi9Tx1VoYk1as', 'password', true, 'Ruben', 'Windmill'),
('user@windmill.dev', true, '$argon2id$v=19$m=4096,t=3,p=1$z0Kg3qyaS14e+YHeihkJLQ$N69flI6yQ/U98pjAHtbNxbdz2f4PrJEi9Tx1VoYk1as', 'password', false, 'User', 'Windmill');
INSERT INTO workspace_invite(workspace_id, email, is_admin) VALUES
('demo', 'ruben@windmill.dev', true);
CREATE TABLE variable (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
path VARCHAR(255),
value VARCHAR(4012) NOT NULL,
is_secret BOOLEAN NOT NULL DEFAULT FALSE,
description VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (workspace_id, path),
CONSTRAINT proper_id CHECK (path ~ '^[ug](\/[\w-]+){2,}$')
);
-- CREATE TABLE oauth(
-- id VARCHAR(150) NOT NULL PRIMARY KEY,
-- owner VARCHAR(50),
-- workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
-- type VARCHAR(50) NOT NULL,
-- refresh_token VARCHAR(255),
-- access_token VARCHAR(255) NOT NULL
-- );
-- CREATE INDEX index_oauth ON oauth (workspace_id, type, owner);
CREATE TYPE ACTION_KIND AS ENUM ('create', 'update', 'delete', 'execute');
CREATE TABLE audit (
workspace_id VARCHAR(50) NOT NULL,
id SERIAL,
timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
username VARCHAR(50) NOT NULL,
operation VARCHAR(50) NOT NULL,
action_kind ACTION_KIND NOT NULL,
resource VARCHAR(255),
parameters JSONB,
PRIMARY KEY (workspace_id, id)
);
CREATE TABLE resource_type (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
name VARCHAR(50),
schema JSONB,
description TEXT,
PRIMARY KEY (workspace_id, name),
CONSTRAINT proper_name CHECK (name ~ '^[\w-]+$')
);
CREATE TABLE resource (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
path VARCHAR(255),
value JSONB,
description TEXT,
resource_type VARCHAR(50) NOT NULL,
PRIMARY KEY (workspace_id, path),
CONSTRAINT proper_id CHECK (path ~ '^[ug](\/[\w-]+){2,}$')
);
INSERT INTO resource_type(workspace_id, name, schema, description) VALUES
('starter', 'postgres', '{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"dbname": {
"description": "The database name",
"type": "string"
},
"user": {
"description": "The postgres username",
"type": "string"
},
"password": {
"description": "The postgres users password",
"type": "string"
},
"sslmode": {
"description": "The sslmode",
"type": "string",
"enum": ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"]
},
"host": {
"description": "The instance host",
"type": "string"
},
"port": {
"description": "The instance port",
"type": "integer"
}
},
"required": ["dbname", "user", "password"]
}', 'A postgres database connection resource')
;
INSERT INTO resource(workspace_id, path, value, description, resource_type) VALUES
('starter', 'g/all/demodb', '{"host": "demodb.service.consul", "dbname": "demodb",
"user": "postgres", "password": "demodb", "sslmode": "disable", "port":"6543"}', 'demodb', 'postgres')
;
CREATE TABLE pipenv (
id SERIAL PRIMARY KEY,
timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_by VARCHAR(50) NOT NULL,
python_version VARCHAR(20),
dependencies VARCHAR(255)[] NOT NULL DEFAULT array[]::varchar[],
pipfile_lock TEXT,
job_id UUID
);
CREATE TABLE schedule(
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
path varchar(255),
edited_by varchar(255) NOT NULL,
edited_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
schedule VARCHAR(255) NOT NULL,
offset_ INTEGER NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
script_path varchar(255),
script_hash BIGINT,
args JSONB,
PRIMARY KEY (workspace_id, path),
CONSTRAINT proper_id CHECK (path ~ '^[ug](\/[\w-]+){2,}$')
);
CREATE TABLE group_ (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
name VARCHAR(50),
summary TEXT,
PRIMARY KEY (workspace_id, name),
CONSTRAINT proper_name CHECK (name ~ '^[\w-]+$')
);
CREATE TABLE usr_to_group(
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
group_ VARCHAR(50) NOT NULL,
usr VARCHAR(50) NOT NULL DEFAULT 'ruben',
CONSTRAINT fk_group FOREIGN KEY(workspace_id, group_) REFERENCES group_(workspace_id, name),
PRIMARY KEY (workspace_id, usr, group_)
);
INSERT INTO group_ SELECT id, 'all', 'The group that always contains all users of this workspace' FROM workspace;
CREATE TABLE worker_ping(
worker VARCHAR(50) PRIMARY KEY,
worker_instance VARCHAR(50) NOT NULL,
ping_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
env_id INTEGER NOT NULL DEFAULT -1,
ip VARCHAR(50) NOT NULL DEFAULT 'NO IP',
jobs_executed INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX worker_ping_on_ping_at ON worker_ping (ping_at);
ALTER TABLE audit ENABLE ROW LEVEL SECURITY;
CREATE POLICY audit_log_see_own ON audit FOR SELECT
USING(audit.username = current_setting('session.user') or current_setting('session.is_admin')::boolean);
-- USING(current_setting('session.is_admin')::boolean);
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT FROM pg_catalog.pg_roles
WHERE rolname = 'app') THEN
CREATE ROLE app LOGIN PASSWORD 'changeme';
END IF;
END
$do$;
GRANT SELECT ON audit TO app;
REVOKE ALL
ON ALL TABLES IN SCHEMA public
FROM PUBLIC;
GRANT ALL
ON ALL TABLES IN SCHEMA public
TO admin;
ALTER DEFAULT PRIVILEGES
FOR ROLE admin
IN SCHEMA public
GRANT ALL ON TABLES TO admin;
INSERT INTO usr_to_group
SELECT workspace_id, 'all', username FROM (SELECT workspace_id, username from usr) as usernames
;
DROP POLICY audit_log_see_own on audit;
GRANT ALL ON audit TO app;
CREATE POLICY see_own ON audit FOR ALL
USING (audit.username = current_setting('session.user'));
GRANT ALL ON queue TO app;
ALTER TABLE queue ENABLE ROW LEVEL SECURITY;
CREATE POLICY see_own ON queue FOR ALL
USING (SPLIT_PART(queue.permissioned_as, '/', 1) = 'u' AND SPLIT_PART(queue.permissioned_as, '/', 2) = current_setting('session.user'));
CREATE POLICY see_member ON queue FOR ALL
USING (SPLIT_PART(queue.permissioned_as, '/', 1) = 'g' AND SPLIT_PART(queue.permissioned_as, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
GRANT ALL ON completed_job TO app;
ALTER TABLE completed_job ENABLE ROW LEVEL SECURITY;
CREATE POLICY see_starter ON completed_job FOR SELECT
USING (completed_job.workspace_id = 'starter');
CREATE POLICY see_own ON completed_job FOR ALL
USING (SPLIT_PART(completed_job.permissioned_as, '/', 1) = 'u' AND SPLIT_PART(completed_job.permissioned_as, '/', 2) = current_setting('session.user'));
CREATE POLICY see_member ON completed_job FOR ALL
USING (SPLIT_PART(completed_job.permissioned_as, '/', 1) = 'g' AND SPLIT_PART(completed_job.permissioned_as, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
GRANT SELECT ON pipenv to app;
GRANT SELECT (email, username, is_admin, workspace_id) ON usr to app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public to app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public to admin;
GRANT SELECT, INSERT ON resource_type to app;
GRANT SELECT ON worker_ping to app;
GRANT SELECT ON worker_ping to admin;
CREATE POLICY schedule ON audit FOR INSERT
WITH CHECK (audit.username LIKE 'schedule-%');
DO
$do$
DECLARE
i text;
arr text[] := array['resource', 'script', 'variable', 'schedule', 'flow'];
BEGIN
FOREACH i IN ARRAY arr
LOOP
EXECUTE FORMAT(
$$
GRANT ALL ON %1$I TO app;
ALTER TABLE %1$I ENABLE ROW LEVEL SECURITY;
CREATE POLICY see_starter ON %1$I FOR SELECT
USING (%1$I.workspace_id = 'starter');
CREATE POLICY see_own ON %1$I FOR ALL
USING (SPLIT_PART(%1$I.path, '/', 1) = 'u' AND SPLIT_PART(%1$I.path, '/', 2) = current_setting('session.user'));
CREATE POLICY see_member ON %1$I FOR ALL
USING (SPLIT_PART(%1$I.path, '/', 1) = 'g' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
ALTER TABLE %1$I
ADD COLUMN extra_perms JSONB NOT NULL DEFAULT '{}';
CREATE INDEX %1$I_extra_perms ON %1$I USING GIN (extra_perms);
CREATE POLICY see_extra_perms_user ON %1$I FOR ALL
USING (extra_perms ? CONCAT('u/', current_setting('session.user')))
WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
CREATE POLICY see_extra_perms_groups ON %1$I FOR ALL
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
WITH CHECK (exists(
SELECT key, value FROM jsonb_each_text(extra_perms)
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
AND value::boolean));
$$,
i
);
END LOOP;
END
$do$;
GRANT ALL ON group_ TO app;
ALTER TABLE group_
ADD COLUMN extra_perms JSONB NOT NULL DEFAULT '{}';
CREATE INDEX group_extra_perms ON group_ USING GIN (extra_perms);
GRANT ALL ON usr_to_group TO app;
ALTER TABLE usr_to_group ENABLE ROW LEVEL SECURITY;
CREATE POLICY see_extra_perms_user ON usr_to_group FOR ALL
USING (true)
WITH CHECK (EXISTS(SELECT 1 FROM group_ WHERE usr_to_group.group_ = group_.name AND usr_to_group.workspace_id = group_.workspace_id AND (group_.extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean));
CREATE POLICY see_extra_perms_groups ON usr_to_group FOR ALL
USING (true)
WITH CHECK (exists(
SELECT f.* FROM group_ g, jsonb_each_text(g.extra_perms) f
WHERE usr_to_group.group_ = g.name AND usr_to_group.workspace_id = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
AND value::boolean));
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT FROM pg_catalog.pg_roles -- SELECT list can be empty for this
WHERE rolname = 'admin') THEN
CREATE ROLE admin WITH BYPASSRLS LOGIN PASSWORD 'changeme';
END IF;
END
$do$;