diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx
index c8373a59039..4586d246c21 100644
--- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx
+++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx
@@ -43,7 +43,7 @@ const PolicyRoles = ({ selectedRoles, onUpdateSelectedRoles }: PolicyRolesProps)
{isLoading &&
}
- {isError &&
}
+ {isError &&
}
{isSuccess && (
{
@@ -54,8 +54,8 @@ const CreateRolePanel = ({ visible, onClose }: CreateRolePanelProps) => {
})
const { mutate: createDatabaseRole, isLoading: isCreating } = useDatabaseRoleCreateMutation({
- onSuccess: (res) => {
- toast.success(`Successfully created new role: ${res.name}`)
+ onSuccess: (_, vars) => {
+ toast.success(`Successfully created new role: ${vars.payload.name}`)
handleClose()
},
})
diff --git a/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx b/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx
index efb5dfdd93c..4422024b5c2 100644
--- a/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx
+++ b/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx
@@ -27,7 +27,7 @@ const DeleteRoleModal = ({ role, visible, onClose }: DeleteRoleModalProps) => {
deleteDatabaseRole({
projectRef: project.ref,
connectionString: project.connectionString,
- id: role.id.toString(),
+ id: role.id,
})
}
diff --git a/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx b/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx
index a138fd08ee9..ae1f6eb55bd 100644
--- a/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx
+++ b/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx
@@ -1,5 +1,4 @@
import * as Tooltip from '@radix-ui/react-tooltip'
-import type { PostgresRole } from '@supabase/postgres-meta'
import { useState } from 'react'
import toast from 'react-hot-toast'
import {
@@ -18,13 +17,14 @@ import {
} from 'ui'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import { PgRole } from 'data/database-roles/database-roles-query'
import { useDatabaseRoleUpdateMutation } from 'data/database-roles/database-role-update-mutation'
import { ROLE_PERMISSIONS } from './Roles.constants'
interface RoleRowProps {
- role: PostgresRole
+ role: PgRole
disabled?: boolean
- onSelectDelete: (role: PostgresRole) => void
+ onSelectDelete: (role: PgRole) => void
}
const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
@@ -33,25 +33,22 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
const { mutate: updateDatabaseRole, isLoading: isUpdating } = useDatabaseRoleUpdateMutation()
- const {
- is_superuser,
- can_login,
- can_create_role,
- can_create_db,
- is_replication_role,
- can_bypass_rls,
- } = role
+ const { isSuperuser, canLogin, canCreateRole, canCreateDb, isReplicationRole, canBypassRls } =
+ role
- const onSaveChanges = async (values: any, { resetForm }: any) => {
+ const onSaveChanges = async (values: Partial, { resetForm }: any) => {
if (!project) return console.error('Project is required')
- const { is_superuser, is_replication_role, ...payload } = values
+ const changed = Object.fromEntries(
+ Object.entries(values).filter(([k, v]) => v !== (role as any)[k])
+ )
+
updateDatabaseRole(
{
projectRef: project.ref,
connectionString: project.connectionString,
id: role.id,
- payload,
+ payload: changed,
},
{
onSuccess: () => {
@@ -66,12 +63,12 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
- {role.active_connections > 0 && (
+ {role.activeConnections > 0 && (
@@ -129,10 +126,10 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
0 ? 'text-foreground' : 'text-foreground-light'
+ role.activeConnections > 0 ? 'text-foreground' : 'text-foreground-light'
}`}
>
- {role.active_connections} connections
+ {role.activeConnections} connections
{!disabled && (
diff --git a/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts b/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts
index 6713ec7e150..377dd9589c6 100644
--- a/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts
+++ b/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts
@@ -33,32 +33,32 @@ export const SYSTEM_ROLES = [
] as const
export const ROLE_PERMISSIONS = {
- can_login: {
+ canLogin: {
disabled: false,
description: 'User can login',
grant_by_dashboard: true,
},
- can_create_role: {
+ canCreateRole: {
disabled: false,
description: 'User can create roles',
grant_by_dashboard: true,
},
- can_create_db: {
+ canCreateDb: {
disabled: false,
description: 'User can create databases',
grant_by_dashboard: true,
},
- can_bypass_rls: {
+ canBypassRls: {
disabled: false,
description: 'User bypasses every row level security policy',
grant_by_dashboard: true,
},
- is_superuser: {
+ isSuperuser: {
disabled: true,
description: 'User is a Superuser',
grant_by_dashboard: false,
},
- is_replication_role: {
+ isReplicationRole: {
disabled: false,
description:
'User can initiate streaming replication and put the system in and out of backup mode',
diff --git a/apps/studio/components/interfaces/Database/Roles/RolesList.tsx b/apps/studio/components/interfaces/Database/Roles/RolesList.tsx
index 302328cac7c..089efbe930e 100644
--- a/apps/studio/components/interfaces/Database/Roles/RolesList.tsx
+++ b/apps/studio/components/interfaces/Database/Roles/RolesList.tsx
@@ -42,19 +42,19 @@ const RolesList = () => {
const roles = sortBy(data ?? [], (r) => r.name.toLocaleLowerCase())
const filteredRoles = (
- filterType === 'active' ? roles.filter((role) => role.active_connections > 0) : roles
+ filterType === 'active' ? roles.filter((role) => role.activeConnections > 0) : roles
).filter((role) => role.name.includes(filterString))
const [supabaseRoles, otherRoles] = partition(filteredRoles, (role) =>
SUPABASE_ROLES.includes(role.name as SUPABASE_ROLE)
)
const totalActiveConnections = roles
- .map((role) => role.active_connections)
+ .map((role) => role.activeConnections)
.reduce((a, b) => a + b, 0)
// order the roles with active connections by number of connections, most connections first
const rolesWithActiveConnections = sortBy(
- roles.filter((role) => role.active_connections > 0),
- (r) => -r.active_connections
+ roles.filter((role) => role.activeConnections > 0),
+ (r) => -r.activeConnections
)
return (
@@ -145,7 +145,7 @@ const RolesList = () => {
Connections by roles:
{rolesWithActiveConnections.map((role) => (
- {role.name}: {role.active_connections}
+ {role.name}: {role.activeConnections}
))}
diff --git a/apps/studio/data/database-roles/database-role-create-mutation.ts b/apps/studio/data/database-roles/database-role-create-mutation.ts
index ecaaa44fa47..7601b21e30d 100644
--- a/apps/studio/data/database-roles/database-role-create-mutation.ts
+++ b/apps/studio/data/database-roles/database-role-create-mutation.ts
@@ -1,12 +1,12 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import pgMeta from '@supabase/pg-meta'
import { toast } from 'react-hot-toast'
-import { post } from 'data/fetchers'
import type { ResponseError } from 'types'
-import { databaseRolesKeys } from './keys'
-import type { components } from 'data/api'
+import { executeSql } from 'data/sql/execute-sql-query'
+import { invalidateRolesQuery } from './database-roles-query'
-type CreateRoleBody = components['schemas']['CreateRoleBody']
+type CreateRoleBody = Parameters
[0]
export type DatabaseRoleCreateVariables = {
projectRef: string
@@ -19,20 +19,14 @@ export async function createDatabaseRole({
connectionString,
payload,
}: DatabaseRoleCreateVariables) {
- let headers = new Headers()
- if (connectionString) headers.set('x-connection-encrypted', connectionString)
-
- const { data, error } = await post('/platform/pg-meta/{ref}/roles', {
- params: {
- header: { 'x-connection-encrypted': connectionString! },
- path: { ref: projectRef },
- },
- body: payload,
- headers,
+ const sql = pgMeta.roles.create(payload).sql
+ const { result } = await executeSql({
+ projectRef,
+ connectionString,
+ sql,
+ queryKey: ['roles', 'create'],
})
-
- if (error) throw error
- return data
+ return result
}
type DatabaseRoleCreateData = Awaited>
@@ -52,7 +46,7 @@ export const useDatabaseRoleCreateMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
- await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef))
+ await invalidateRolesQuery(queryClient, projectRef)
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
diff --git a/apps/studio/data/database-roles/database-role-delete-mutation.ts b/apps/studio/data/database-roles/database-role-delete-mutation.ts
index ac0569614ab..c2e7ddda4e7 100644
--- a/apps/studio/data/database-roles/database-role-delete-mutation.ts
+++ b/apps/studio/data/database-roles/database-role-delete-mutation.ts
@@ -1,35 +1,34 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import pgMeta from '@supabase/pg-meta'
import { toast } from 'react-hot-toast'
-import { del } from 'data/fetchers'
import type { ResponseError } from 'types'
-import { databaseRolesKeys } from './keys'
+import { executeSql } from 'data/sql/execute-sql-query'
+import { invalidateRolesQuery } from './database-roles-query'
+
+type DropRoleBody = Parameters[1]
export type DatabaseRoleDeleteVariables = {
projectRef: string
connectionString?: string
- id: string
+ id: number
+ payload?: DropRoleBody
}
export async function deleteDatabaseRole({
projectRef,
connectionString,
id,
+ payload,
}: DatabaseRoleDeleteVariables) {
- let headers = new Headers()
- if (connectionString) headers.set('x-connection-encrypted', connectionString)
-
- const { data, error } = await del('/platform/pg-meta/{ref}/roles', {
- params: {
- header: { 'x-connection-encrypted': connectionString! },
- path: { ref: projectRef },
- query: { id },
- },
- headers,
+ const sql = pgMeta.roles.remove({ id }, payload).sql
+ const { result } = await executeSql({
+ projectRef,
+ connectionString,
+ sql,
+ queryKey: ['roles', 'delete'],
})
-
- if (error) throw error
- return data
+ return result
}
type DatabaseRoleDeleteData = Awaited>
@@ -49,7 +48,7 @@ export const useDatabaseRoleDeleteMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
- await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef))
+ await invalidateRolesQuery(queryClient, projectRef)
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
diff --git a/apps/studio/data/database-roles/database-role-update-mutation.ts b/apps/studio/data/database-roles/database-role-update-mutation.ts
index c8876fb55e7..a0d90520269 100644
--- a/apps/studio/data/database-roles/database-role-update-mutation.ts
+++ b/apps/studio/data/database-roles/database-role-update-mutation.ts
@@ -1,12 +1,12 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import pgMeta from '@supabase/pg-meta'
import { toast } from 'react-hot-toast'
-import { patch } from 'data/fetchers'
import type { ResponseError } from 'types'
-import { databaseRolesKeys } from './keys'
-import type { components } from 'data/api'
+import { executeSql } from 'data/sql/execute-sql-query'
+import { invalidateRolesQuery } from './database-roles-query'
-type UpdateRoleBody = components['schemas']['UpdateRoleBody']
+type UpdateRoleBody = Parameters[1]
export type DatabaseRoleUpdateVariables = {
projectRef: string
@@ -21,21 +21,14 @@ export async function updateDatabaseRole({
id,
payload,
}: DatabaseRoleUpdateVariables) {
- let headers = new Headers()
- if (connectionString) headers.set('x-connection-encrypted', connectionString)
-
- const { data, error } = await patch('/platform/pg-meta/{ref}/roles', {
- params: {
- header: { 'x-connection-encrypted': connectionString! },
- path: { ref: projectRef },
- query: { id },
- },
- body: payload,
- headers,
+ const sql = pgMeta.roles.update({ id }, payload).sql
+ const { result } = await executeSql({
+ projectRef,
+ connectionString,
+ sql,
+ queryKey: ['roles', 'update'],
})
-
- if (error) throw error
- return data
+ return result
}
type DatabaseRoleUpdateData = Awaited>
@@ -55,7 +48,7 @@ export const useDatabaseRoleUpdateMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
- await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef))
+ await invalidateRolesQuery(queryClient, projectRef)
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
diff --git a/apps/studio/data/database-roles/database-roles-query.ts b/apps/studio/data/database-roles/database-roles-query.ts
index fc107b656a2..e347af6f236 100644
--- a/apps/studio/data/database-roles/database-roles-query.ts
+++ b/apps/studio/data/database-roles/database-roles-query.ts
@@ -1,49 +1,41 @@
-import { UseQueryOptions, useQuery } from '@tanstack/react-query'
-import type { PostgresRole } from '@supabase/postgres-meta'
+import pgMeta from '@supabase/pg-meta'
+import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
+import { z } from 'zod'
-import { get } from 'data/fetchers'
-import type { ResponseError } from 'types'
-import { databaseRolesKeys } from './keys'
+import { ExecuteSqlData, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
+import { sqlKeys } from 'data/sql/keys'
export type DatabaseRolesVariables = {
projectRef?: string
connectionString?: string
}
-export async function getDatabaseRoles(
- { projectRef, connectionString }: DatabaseRolesVariables,
- signal?: AbortSignal
-) {
- if (!projectRef) throw new Error('projectRef is required')
+export type PgRole = z.infer
- let headers = new Headers()
- if (connectionString) headers.set('x-connection-encrypted', connectionString)
+const pgMetaRolesList = pgMeta.roles.list()
- const { data, error } = await get('/platform/pg-meta/{ref}/roles', {
- params: {
- header: { 'x-connection-encrypted': connectionString! },
- path: { ref: projectRef },
- },
- headers,
- signal,
- })
-
- if (error) throw error
- return data as PostgresRole[]
-}
-
-export type DatabaseRolesData = Awaited>
-export type DatabaseRolesError = ResponseError
+export type DatabaseRolesData = z.infer
+export type DatabaseRolesError = unknown
export const useDatabaseRolesQuery = (
{ projectRef, connectionString }: DatabaseRolesVariables,
- { enabled = true, ...options }: UseQueryOptions = {}
+ options: UseQueryOptions = {}
) =>
- useQuery(
- databaseRolesKeys.list(projectRef),
- ({ signal }) => getDatabaseRoles({ projectRef, connectionString }, signal),
+ useExecuteSqlQuery(
{
- enabled: enabled && typeof projectRef !== 'undefined',
+ projectRef,
+ connectionString,
+ sql: pgMetaRolesList.sql,
+ queryKey: ['roles', 'list'],
+ },
+ {
+ select(data) {
+ return data.result
+ },
...options,
}
)
+
+export function invalidateRolesQuery(client: QueryClient, projectRef: string | undefined) {
+ return client.invalidateQueries(sqlKeys.query(projectRef, ['roles', 'list']))
+}
diff --git a/apps/studio/data/database-roles/keys.ts b/apps/studio/data/database-roles/keys.ts
deleted file mode 100644
index 34ca7337bf4..00000000000
--- a/apps/studio/data/database-roles/keys.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const databaseRolesKeys = {
- list: (projectRef: string | undefined) => ['projects', projectRef, 'database-roles'] as const,
-}
diff --git a/apps/studio/data/database/schema-create-mutation.ts b/apps/studio/data/database/schema-create-mutation.ts
index 1bd5a6cab6a..81499a34b97 100644
--- a/apps/studio/data/database/schema-create-mutation.ts
+++ b/apps/studio/data/database/schema-create-mutation.ts
@@ -3,7 +3,6 @@ import pgMeta from '@supabase/pg-meta'
import { toast } from 'react-hot-toast'
import type { ResponseError } from 'types'
-import { databaseKeys } from './keys'
import { executeSql } from 'data/sql/execute-sql-query'
import { invalidateSchemasQuery } from './schemas-query'
diff --git a/apps/studio/pages/project/[ref]/auth/column-privileges.tsx b/apps/studio/pages/project/[ref]/auth/column-privileges.tsx
index b3aa68fb561..40d8c3d0d7b 100644
--- a/apps/studio/pages/project/[ref]/auth/column-privileges.tsx
+++ b/apps/studio/pages/project/[ref]/auth/column-privileges.tsx
@@ -28,7 +28,7 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
-import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query'
+import { PgRole, useDatabaseRolesQuery } from 'data/database-roles/database-roles-query'
import { useColumnPrivilegesQuery } from 'data/privileges/column-privileges-query'
import { useTablePrivilegesQuery } from 'data/privileges/table-privileges-query'
import { useTablesQuery } from 'data/tables/tables-query'
@@ -133,9 +133,8 @@ const PrivilegesPage: NextPageWithLayout = () => {
[allColumnPrivileges, selectedRole, selectedSchema, selectedTable]
)
- const rolesList =
- allRoles?.filter((role: PostgresRole) => EDITABLE_ROLES.includes(role.name)) ?? []
- const roles = rolesList.map((role: PostgresRole) => role.name)
+ const rolesList = allRoles?.filter((role: PgRole) => EDITABLE_ROLES.includes(role.name)) ?? []
+ const roles = rolesList.map((role: PgRole) => role.name)
const table = tableList?.find(
(table) => table.schema === selectedSchema && table.name === selectedTable
diff --git a/apps/studio/styles/code.scss b/apps/studio/styles/code.scss
index 3009884e0a3..6bc7f701e15 100644
--- a/apps/studio/styles/code.scss
+++ b/apps/studio/styles/code.scss
@@ -90,7 +90,10 @@
opacity: 0;
position: absolute;
visibility: hidden;
- transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out, bottom 200ms ease-in-out;
+ transition:
+ opacity 200ms ease-in-out,
+ visibility 200ms ease-in-out,
+ bottom 200ms ease-in-out;
button {
@apply rounded text-xs;
diff --git a/apps/studio/styles/editor.scss b/apps/studio/styles/editor.scss
index 4a76731d766..9c9236d76b3 100644
--- a/apps/studio/styles/editor.scss
+++ b/apps/studio/styles/editor.scss
@@ -27,4 +27,4 @@
input {
@apply pr-10;
}
-}
\ No newline at end of file
+}
diff --git a/apps/studio/styles/graphiql-base.scss b/apps/studio/styles/graphiql-base.scss
index 1ef8ebdf1d9..eebcac28006 100644
--- a/apps/studio/styles/graphiql-base.scss
+++ b/apps/studio/styles/graphiql-base.scss
@@ -132,8 +132,7 @@ button.graphiql-tab-add > svg {
/* The query editor and the toolbar */
.graphiql-container .graphiql-query-editor {
- border-bottom: 1px solid
- hsla(var(--color-neutral), var(--alpha-background-heavy));
+ border-bottom: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
padding: var(--px-16);
column-gap: var(--px-16);
display: flex;
@@ -175,9 +174,7 @@ button.graphiql-tab-add > svg {
}
/* The tab buttons to switch between editor tools */
-.graphiql-container
- .graphiql-editor-tools
- > button:not(.graphiql-toggle-editor-tools) {
+.graphiql-container .graphiql-editor-tools > button:not(.graphiql-toggle-editor-tools) {
padding: var(--px-8) var(--px-12);
}
@@ -219,14 +216,12 @@ button.graphiql-tab-add > svg {
/* The footer below the response view */
.graphiql-container .graphiql-footer {
- border-top: 1px solid
- hsla(var(--color-neutral), var(--alpha-background-heavy));
+ border-top: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
}
/* The plugin container */
.graphiql-container .graphiql-plugin {
- border-left: 1px solid
- hsla(var(--color-neutral), var(--alpha-background-heavy));
+ border-left: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
flex: 1;
overflow-y: auto;
padding: var(--px-16);
@@ -239,8 +234,7 @@ button.graphiql-tab-add > svg {
}
.graphiql-horizontal-drag-bar:hover::after {
- border: var(--px-2) solid
- hsla(var(--color-neutral), var(--alpha-background-heavy));
+ border: var(--px-2) solid hsla(var(--color-neutral), var(--alpha-background-heavy));
border-radius: var(--border-radius-2);
content: '';
display: block;
@@ -292,8 +286,7 @@ button.graphiql-tab-add > svg {
/* A section inside the settings dialog */
.graphiql-dialog .graphiql-dialog-section {
align-items: center;
- border-top: 1px solid
- hsla(var(--color-neutral), var(--alpha-background-heavy));
+ border-top: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
display: flex;
justify-content: space-between;
padding: var(--px-24);
diff --git a/apps/studio/styles/markdown-preview.scss b/apps/studio/styles/markdown-preview.scss
index 742983e6b76..07e97c62972 100644
--- a/apps/studio/styles/markdown-preview.scss
+++ b/apps/studio/styles/markdown-preview.scss
@@ -2,7 +2,7 @@
@media (prefers-color-scheme: dark) {
.markdown-body,
- [data-theme="dark"] {
+ [data-theme='dark'] {
/*dark*/
color-scheme: dark;
--color-prettylights-syntax-comment: #8b949e;
@@ -42,14 +42,14 @@
--color-canvas-subtle: #161b22;
--color-border-default: #30363d;
--color-border-muted: #21262d;
- --color-neutral-muted: rgba(110,118,129,0.4);
+ --color-neutral-muted: rgba(110, 118, 129, 0.4);
--color-accent-fg: #2f81f7;
--color-accent-emphasis: #1f6feb;
--color-success-fg: #3fb950;
--color-success-emphasis: #238636;
--color-attention-fg: #d29922;
--color-attention-emphasis: #9e6a03;
- --color-attention-subtle: rgba(187,128,9,0.15);
+ --color-attention-subtle: rgba(187, 128, 9, 0.15);
--color-danger-fg: #f85149;
--color-danger-emphasis: #da3633;
--color-done-fg: #a371f7;
@@ -59,7 +59,7 @@
@media (prefers-color-scheme: light) {
.markdown-body,
- [data-theme="light"] {
+ [data-theme='light'] {
/*light*/
color-scheme: light;
--color-prettylights-syntax-comment: #57606a;
@@ -92,14 +92,14 @@
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
- --color-fg-default: #1F2328;
+ --color-fg-default: #1f2328;
--color-fg-muted: #656d76;
--color-fg-subtle: #6e7781;
--color-canvas-default: #ffffff;
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
- --color-border-muted: hsla(210,18%,87%,1);
- --color-neutral-muted: rgba(175,184,193,0.2);
+ --color-border-muted: hsla(210, 18%, 87%, 1);
+ --color-neutral-muted: rgba(175, 184, 193, 0.2);
--color-accent-fg: #0969da;
--color-accent-emphasis: #0969da;
--color-success-fg: #1a7f37;
@@ -118,7 +118,8 @@
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
margin: 0;
- font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial,
+ sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
@@ -181,9 +182,9 @@
}
.markdown-body h1 {
- margin: .67em 0;
+ margin: 0.67em 0;
font-weight: var(--base-text-weight-semibold, 600);
- padding-bottom: .3em;
+ padding-bottom: 0.3em;
font-size: 2em;
@apply border-b;
}
@@ -237,7 +238,7 @@
overflow: hidden;
background: transparent;
@apply border-b;
- height: .25em;
+ height: 0.25em;
padding: 0;
margin: 24px 0;
background-color: var(--color-border-default);
@@ -253,33 +254,33 @@
line-height: inherit;
}
-.markdown-body [type=button],
-.markdown-body [type=reset],
-.markdown-body [type=submit] {
+.markdown-body [type='button'],
+.markdown-body [type='reset'],
+.markdown-body [type='submit'] {
-webkit-appearance: button;
appearance: button;
}
-.markdown-body [type=checkbox],
-.markdown-body [type=radio] {
+.markdown-body [type='checkbox'],
+.markdown-body [type='radio'] {
box-sizing: border-box;
padding: 0;
}
-.markdown-body [type=number]::-webkit-inner-spin-button,
-.markdown-body [type=number]::-webkit-outer-spin-button {
+.markdown-body [type='number']::-webkit-inner-spin-button,
+.markdown-body [type='number']::-webkit-outer-spin-button {
height: auto;
}
-.markdown-body [type=search]::-webkit-search-cancel-button,
-.markdown-body [type=search]::-webkit-search-decoration {
+.markdown-body [type='search']::-webkit-search-cancel-button,
+.markdown-body [type='search']::-webkit-search-decoration {
-webkit-appearance: none;
appearance: none;
}
.markdown-body ::-webkit-input-placeholder {
color: inherit;
- opacity: .54;
+ opacity: 0.54;
}
.markdown-body ::-webkit-file-upload-button {
@@ -299,13 +300,13 @@
.markdown-body hr::before {
display: table;
- content: "";
+ content: '';
}
.markdown-body hr::after {
display: table;
clear: both;
- content: "";
+ content: '';
}
.markdown-body table {
@@ -326,30 +327,30 @@
cursor: pointer;
}
-.markdown-body details:not([open])>*:not(summary) {
+.markdown-body details:not([open]) > *:not(summary) {
display: none !important;
}
.markdown-body a:focus,
-.markdown-body [role=button]:focus,
-.markdown-body input[type=radio]:focus,
-.markdown-body input[type=checkbox]:focus {
+.markdown-body [role='button']:focus,
+.markdown-body input[type='radio']:focus,
+.markdown-body input[type='checkbox']:focus {
outline: 2px solid var(--color-accent-fg);
outline-offset: -2px;
box-shadow: none;
}
.markdown-body a:focus:not(:focus-visible),
-.markdown-body [role=button]:focus:not(:focus-visible),
-.markdown-body input[type=radio]:focus:not(:focus-visible),
-.markdown-body input[type=checkbox]:focus:not(:focus-visible) {
+.markdown-body [role='button']:focus:not(:focus-visible),
+.markdown-body input[type='radio']:focus:not(:focus-visible),
+.markdown-body input[type='checkbox']:focus:not(:focus-visible) {
outline: solid 1px transparent;
}
.markdown-body a:focus-visible,
-.markdown-body [role=button]:focus-visible,
-.markdown-body input[type=radio]:focus-visible,
-.markdown-body input[type=checkbox]:focus-visible {
+.markdown-body [role='button']:focus-visible,
+.markdown-body input[type='radio']:focus-visible,
+.markdown-body input[type='checkbox']:focus-visible {
outline: 2px solid var(--color-accent-fg);
outline-offset: -2px;
box-shadow: none;
@@ -357,17 +358,24 @@
.markdown-body a:not([class]):focus,
.markdown-body a:not([class]):focus-visible,
-.markdown-body input[type=radio]:focus,
-.markdown-body input[type=radio]:focus-visible,
-.markdown-body input[type=checkbox]:focus,
-.markdown-body input[type=checkbox]:focus-visible {
+.markdown-body input[type='radio']:focus,
+.markdown-body input[type='radio']:focus-visible,
+.markdown-body input[type='checkbox']:focus,
+.markdown-body input[type='checkbox']:focus-visible {
outline-offset: 0;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
- font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
+ font:
+ 11px ui-monospace,
+ SFMono-Regular,
+ SF Mono,
+ Menlo,
+ Consolas,
+ Liberation Mono,
+ monospace;
line-height: 10px;
color: var(--color-fg-default);
vertical-align: middle;
@@ -392,7 +400,7 @@
.markdown-body h2 {
font-weight: var(--base-text-weight-semibold, 600);
- padding-bottom: .3em;
+ padding-bottom: 0.3em;
font-size: 1.5em;
@apply border-b;
}
@@ -409,12 +417,12 @@
.markdown-body h5 {
font-weight: var(--base-text-weight-semibold, 600);
- font-size: .875em;
+ font-size: 0.875em;
}
.markdown-body h6 {
font-weight: var(--base-text-weight-semibold, 600);
- font-size: .85em;
+ font-size: 0.85em;
color: var(--color-fg-muted);
}
@@ -427,7 +435,7 @@
margin: 0;
padding: 0 1em;
color: var(--color-fg-muted);
- border-left: .25em solid var(--color-border-default);
+ border-left: 0.25em solid var(--color-border-default);
}
.markdown-body ul,
@@ -456,14 +464,28 @@
.markdown-body tt,
.markdown-body code,
.markdown-body samp {
- font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
+ font-family:
+ ui-monospace,
+ SFMono-Regular,
+ SF Mono,
+ Menlo,
+ Consolas,
+ Liberation Mono,
+ monospace;
font-size: 12px;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
- font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
+ font-family:
+ ui-monospace,
+ SFMono-Regular,
+ SF Mono,
+ Menlo,
+ Consolas,
+ Liberation Mono,
+ monospace;
font-size: 12px;
word-wrap: normal;
}
@@ -488,20 +510,20 @@
.markdown-body::before {
display: table;
- content: "";
+ content: '';
}
.markdown-body::after {
display: table;
clear: both;
- content: "";
+ content: '';
}
-.markdown-body>*:first-child {
+.markdown-body > *:first-child {
margin-top: 0 !important;
}
-.markdown-body>*:last-child {
+.markdown-body > *:last-child {
margin-bottom: 0 !important;
}
@@ -537,11 +559,11 @@
margin-bottom: 16px;
}
-.markdown-body blockquote>:first-child {
+.markdown-body blockquote > :first-child {
margin-top: 0;
}
-.markdown-body blockquote>:last-child {
+.markdown-body blockquote > :last-child {
margin-bottom: 0;
}
@@ -586,7 +608,7 @@
.markdown-body h5 code,
.markdown-body h6 tt,
.markdown-body h6 code {
- padding: 0 .2em;
+ padding: 0 0.2em;
font-size: inherit;
}
@@ -620,27 +642,27 @@
list-style-type: none;
}
-.markdown-body ol[type="a s"] {
+.markdown-body ol[type='a s'] {
list-style-type: lower-alpha;
}
-.markdown-body ol[type="A s"] {
+.markdown-body ol[type='A s'] {
list-style-type: upper-alpha;
}
-.markdown-body ol[type="i s"] {
+.markdown-body ol[type='i s'] {
list-style-type: lower-roman;
}
-.markdown-body ol[type="I s"] {
+.markdown-body ol[type='I s'] {
list-style-type: upper-roman;
}
-.markdown-body ol[type="1"] {
+.markdown-body ol[type='1'] {
list-style-type: decimal;
}
-.markdown-body div>ol:not([type]) {
+.markdown-body div > ol:not([type]) {
list-style-type: decimal;
}
@@ -652,12 +674,12 @@
margin-bottom: 0;
}
-.markdown-body li>p {
+.markdown-body li > p {
margin-top: 16px;
}
-.markdown-body li+li {
- margin-top: .25em;
+.markdown-body li + li {
+ margin-top: 0.25em;
}
.markdown-body dl {
@@ -687,7 +709,7 @@
border: 1px solid var(--color-border-default);
}
-.markdown-body table td>:last-child {
+.markdown-body table td > :last-child {
margin-bottom: 0;
}
@@ -704,11 +726,11 @@
background-color: transparent;
}
-.markdown-body img[align=right] {
+.markdown-body img[align='right'] {
padding-left: 20px;
}
-.markdown-body img[align=left] {
+.markdown-body img[align='left'] {
padding-right: 20px;
}
@@ -723,7 +745,7 @@
overflow: hidden;
}
-.markdown-body span.frame>span {
+.markdown-body span.frame > span {
display: block;
float: left;
width: auto;
@@ -751,7 +773,7 @@
clear: both;
}
-.markdown-body span.align-center>span {
+.markdown-body span.align-center > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
@@ -769,7 +791,7 @@
clear: both;
}
-.markdown-body span.align-right>span {
+.markdown-body span.align-right > span {
display: block;
margin: 13px 0 0;
overflow: hidden;
@@ -799,7 +821,7 @@
overflow: hidden;
}
-.markdown-body span.float-right>span {
+.markdown-body span.float-right > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
@@ -808,7 +830,7 @@
.markdown-body code,
.markdown-body tt {
- padding: .2em .4em;
+ padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
white-space: break-spaces;
@@ -833,7 +855,7 @@
font-size: 100%;
}
-.markdown-body pre>code {
+.markdown-body pre > code {
padding: 0;
margin: 0;
word-break: normal;
@@ -903,11 +925,11 @@
}
.markdown-body [data-footnote-ref]::before {
- content: "[";
+ content: '[';
}
.markdown-body [data-footnote-ref]::after {
- content: "]";
+ content: ']';
}
.markdown-body .footnotes {
@@ -937,7 +959,7 @@
bottom: -8px;
left: -24px;
pointer-events: none;
- content: "";
+ content: '';
border: 2px solid var(--color-accent-emphasis);
border-radius: 6px;
}
@@ -1073,7 +1095,7 @@
.markdown-body g-emoji {
display: inline-block;
min-width: 1ch;
- font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
+ font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
font-size: 1em;
font-style: normal !important;
font-weight: var(--base-text-weight-normal, 400);
@@ -1098,7 +1120,7 @@
cursor: pointer;
}
-.markdown-body .task-list-item+.task-list-item {
+.markdown-body .task-list-item + .task-list-item {
margin-top: 4px;
}
@@ -1107,12 +1129,12 @@
}
.markdown-body .task-list-item-checkbox {
- margin: 0 .2em .25em -1.4em;
+ margin: 0 0.2em 0.25em -1.4em;
vertical-align: middle;
}
.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
- margin: 0 -1.6em .25em .2em;
+ margin: 0 -1.6em 0.25em 0.2em;
}
.markdown-body .contains-task-list {
@@ -1136,14 +1158,14 @@
padding: var(--base-size-8) var(--base-size-16);
margin-bottom: 16px;
color: inherit;
- border-left: .25em solid var(--color-border-default);
+ border-left: 0.25em solid var(--color-border-default);
}
-.markdown-body .markdown-alert>:first-child {
+.markdown-body .markdown-alert > :first-child {
margin-top: 0;
}
-.markdown-body .markdown-alert>:last-child {
+.markdown-body .markdown-alert > :last-child {
margin-bottom: 0;
}
@@ -1192,4 +1214,4 @@
.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title {
color: var(--color-danger-fg);
-}
\ No newline at end of file
+}
diff --git a/apps/studio/styles/reactflow.scss b/apps/studio/styles/reactflow.scss
index 083b194f818..3907847eae9 100644
--- a/apps/studio/styles/reactflow.scss
+++ b/apps/studio/styles/reactflow.scss
@@ -15,4 +15,4 @@
to {
stroke-dashoffset: 100;
}
-}
\ No newline at end of file
+}
diff --git a/apps/studio/styles/toast.scss b/apps/studio/styles/toast.scss
index 03e7719f9a7..4463dff48b1 100644
--- a/apps/studio/styles/toast.scss
+++ b/apps/studio/styles/toast.scss
@@ -64,7 +64,9 @@
margin-bottom: 1rem;
padding: 8px;
border-radius: 1px;
- box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 15px 0 rgba(0, 0, 0, 0.05);
+ box-shadow:
+ 0 1px 10px 0 rgba(0, 0, 0, 0.1),
+ 0 2px 15px 0 rgba(0, 0, 0, 0.05);
display: -ms-flexbox;
display: flex;
-ms-flex-pack: justify;
@@ -559,4 +561,4 @@
}
}
-/*# sourceMappingURL=ReactToastify.css.map */
\ No newline at end of file
+/*# sourceMappingURL=ReactToastify.css.map */
diff --git a/packages/pg-meta/src/index.ts b/packages/pg-meta/src/index.ts
index f1262d58ceb..aaadfe36832 100644
--- a/packages/pg-meta/src/index.ts
+++ b/packages/pg-meta/src/index.ts
@@ -1,5 +1,7 @@
+import roles from './pg-meta-roles'
import schemas from './pg-meta-schemas'
export default {
+ roles,
schemas,
}
diff --git a/packages/pg-meta/src/pg-meta-roles.ts b/packages/pg-meta/src/pg-meta-roles.ts
new file mode 100644
index 00000000000..f3c5ca391c5
--- /dev/null
+++ b/packages/pg-meta/src/pg-meta-roles.ts
@@ -0,0 +1,253 @@
+import { ident, literal } from 'pg-format'
+import { ROLES_SQL } from './sql/roles'
+import { z } from 'zod'
+
+const pgRoleZod = z.object({
+ id: z.number(),
+ name: z.string(),
+ isSuperuser: z.boolean(),
+ canCreateDb: z.boolean(),
+ canCreateRole: z.boolean(),
+ inheritRole: z.boolean(),
+ canLogin: z.boolean(),
+ isReplicationRole: z.boolean(),
+ canBypassRls: z.boolean(),
+ activeConnections: z.number(),
+ connectionLimit: z.number(),
+ validUntil: z.union([z.string(), z.null()]),
+ config: z.record(z.string(), z.string()),
+})
+const pgRoleArrayZod = z.array(pgRoleZod)
+const pgRoleOptionalZod = z.optional(pgRoleZod)
+
+function list({
+ includeDefaultRoles: includeDefaultRoles = false,
+ limit,
+ offset,
+}: {
+ includeDefaultRoles?: boolean
+ limit?: number
+ offset?: number
+} = {}): {
+ sql: string
+ zod: typeof pgRoleArrayZod
+} {
+ let sql = `
+with
+ roles as (${ROLES_SQL})
+select
+ *
+from
+ roles
+where
+ true
+`
+ if (!includeDefaultRoles) {
+ // All default/predefined roles start with pg_: https://www.postgresql.org/docs/15/predefined-roles.html
+ // The pg_ prefix is also reserved:
+ //
+ // ```
+ // postgres=# create role pg_myrole;
+ // ERROR: role name "pg_myrole" is reserved
+ // DETAIL: Role names starting with "pg_" are reserved.
+ // ```
+ sql += ` and not pg_catalog.starts_with(name, 'pg_')`
+ }
+ if (limit) {
+ sql += ` limit ${limit}`
+ }
+ if (offset) {
+ sql += ` offset ${offset}`
+ }
+ return {
+ sql,
+ zod: pgRoleArrayZod,
+ }
+}
+
+function retrieve({ id }: { id: number }): { sql: string; zod: typeof pgRoleOptionalZod }
+function retrieve({ name }: { name: string }): { sql: string; zod: typeof pgRoleOptionalZod }
+function retrieve({ id, name }: { id?: number; name?: string }): { sql: string; zod: typeof pgRoleOptionalZod } {
+ if (id) {
+ const sql = `${ROLES_SQL} where r.oid = ${literal(id)};`
+ return {
+ sql,
+ zod: pgRoleOptionalZod,
+ }
+ } else {
+ const sql = `${ROLES_SQL} where rolname = ${literal(name)};`
+ return {
+ sql,
+ zod: pgRoleOptionalZod,
+ }
+ }
+}
+
+type RoleCreateParams = {
+ name: string,
+ isSuperuser?: boolean,
+ canCreateDb?: boolean,
+ canCreateRole?: boolean,
+ inheritRole?: boolean,
+ canLogin?: boolean,
+ isReplicationRole?: boolean,
+ canBypassRls?: boolean,
+ connectionLimit?: number,
+ password?: string,
+ validUntil?: string,
+ memberOf?: string[],
+ members?: string[],
+ admins?: string[],
+ config?: Record,
+}
+function create({
+ name,
+ isSuperuser = false,
+ canCreateDb = false,
+ canCreateRole = false,
+ inheritRole = true,
+ canLogin = false,
+ isReplicationRole = false,
+ canBypassRls = false,
+ connectionLimit = -1,
+ password,
+ validUntil,
+ memberOf = [],
+ members = [],
+ admins = [],
+ config = {},
+}: RoleCreateParams): { sql: string } {
+ const sql = `
+create role ${ident(name)}
+ ${isSuperuser ? 'superuser' : ''}
+ ${canCreateDb ? 'createdb' : ''}
+ ${canCreateRole ? 'createrole' : ''}
+ ${inheritRole ? '' : 'noinherit'}
+ ${canLogin ? 'login' : ''}
+ ${isReplicationRole ? 'replication' : ''}
+ ${canBypassRls ? 'bypassrls' : ''}
+ connection limit ${connectionLimit}
+ ${password === undefined ? '' : `password ${literal(password)}`}
+ ${validUntil === undefined ? '' : `valid until ${literal(validUntil)}`}
+ ${memberOf.length === 0 ? '' : `in role ${memberOf.map(ident).join(',')}`}
+ ${members.length === 0 ? '' : `role ${members.map(ident).join(',')}`}
+ ${admins.length === 0 ? '' : `admin ${admins.map(ident).join(',')}`}
+ ;
+${Object.entries(config).map(([param, value]) => `alter role ${ident(name)} set ${ident(param)} = ${literal(value)};`).join('\n')}
+`
+ return { sql }
+}
+
+type RoleUpdateParams = {
+ name?: string,
+ isSuperuser?: boolean,
+ canCreateDb?: boolean,
+ canCreateRole?: boolean,
+ inheritRole?: boolean,
+ canLogin?: boolean,
+ isReplicationRole?: boolean,
+ canBypassRls?: boolean,
+ connectionLimit?: number,
+ password?: string,
+ validUntil?: string,
+}
+function update({ id }: { id: number }, params: RoleUpdateParams): { sql: string }
+function update({ name }: { name: string }, params: RoleUpdateParams): { sql: string }
+function update(
+ {
+ id,
+ name,
+ }: {
+ id?: number
+ name?: string
+ },
+ {
+ name: newName,
+ isSuperuser,
+ canCreateDb,
+ canCreateRole,
+ inheritRole,
+ canLogin,
+ isReplicationRole,
+ canBypassRls,
+ connectionLimit,
+ password,
+ validUntil,
+ }: RoleUpdateParams): { sql: string } {
+ const sql = `
+do $$
+declare
+ id oid := ${id === undefined ? `${literal(name)}::regrole` : literal(id)};
+ old record;
+begin
+ select * into old from pg_roles where oid = id;
+ if old is null then
+ raise exception 'Cannot find role with id %', id;
+ end if;
+
+ execute(format('alter role %I
+ ${isSuperuser === undefined ? '' : isSuperuser ? 'superuser' : 'nosuperuser'}
+ ${canCreateDb === undefined ? '' : canCreateDb ? 'createdb' : 'nocreatedb'}
+ ${canCreateRole === undefined ? '' : canCreateRole ? 'createrole' : 'nocreaterole'}
+ ${inheritRole === undefined ? '' : inheritRole ? 'inherit' : 'noinherit'}
+ ${canLogin === undefined ? '' : canLogin ? 'login' : 'nologin'}
+ ${isReplicationRole === undefined ? '' : isReplicationRole ? 'replication' : 'noreplication'}
+ ${canBypassRls === undefined ? '' : canBypassRls ? 'bypassrls' : 'nobypassrls'}
+ ${connectionLimit === undefined ? '' : `connection limit ${literal(connectionLimit)}`}
+ ${password === undefined ? '' : `password ${literal(password)}`}
+ ${validUntil === undefined ? '' : `valid until ${literal(validUntil)}`}
+ ', old.rolname));
+
+ ${newName === undefined ? '' : `
+ -- Using the same name in the rename clause gives an error, so only do it if the new name is different.
+ if new_name != old.nspname then
+ execute(format('alter role %I rename to ${ident(newName)};', old.nspname));
+ end if;
+ `}
+end
+$$;
+`
+ return { sql }
+}
+
+type RoleRemoveParams = {
+ ifExists?: boolean
+}
+function remove({ id }: { id: number }, params?: RoleRemoveParams): { sql: string }
+function remove({ name }: { name: string }, params?: RoleRemoveParams): { sql: string }
+function remove(
+ {
+ id,
+ name,
+ }: {
+ id?: number
+ name?: string
+ },
+ { ifExists = false }: RoleRemoveParams = {}
+): { sql: string } {
+ const sql = `
+do $$
+declare
+ id oid := ${id === undefined ? `${literal(name)}::regrole` : literal(id)};
+ old record;
+begin
+ select * into old from pg_roles where oid = id;
+ if old is null then
+ raise exception 'Cannot find role with id %', id;
+ end if;
+
+ execute(format('drop role ${ifExists ? 'if exists' : ''} %I;', old.rolname));
+end
+$$;
+`
+ return { sql }
+}
+
+export default {
+ list,
+ retrieve,
+ create,
+ update,
+ remove,
+ zod: pgRoleZod,
+}
diff --git a/packages/pg-meta/src/sql/roles.ts b/packages/pg-meta/src/sql/roles.ts
new file mode 100644
index 00000000000..088691f547d
--- /dev/null
+++ b/packages/pg-meta/src/sql/roles.ts
@@ -0,0 +1,46 @@
+export const ROLES_SQL = /* SQL */ `
+-- Can't use pg_authid here since some managed Postgres providers don't expose it
+-- https://github.com/supabase/postgres-meta/issues/212
+
+select
+ r.oid :: int8 as id,
+ rolname as name,
+ rolsuper as "isSuperuser",
+ rolcreatedb as "canCreateDb",
+ rolcreaterole as "canCreateRole",
+ rolinherit as "inheritRole",
+ rolcanlogin as "canLogin",
+ rolreplication as "isReplicationRole",
+ rolbypassrls as "canBypassRls",
+ (
+ select
+ count(*)
+ from
+ pg_stat_activity
+ where
+ r.rolname = pg_stat_activity.usename
+ ) as "activeConnections",
+ case when rolconnlimit = -1 then current_setting('max_connections') :: int8
+ else rolconnlimit
+ end as "connectionLimit",
+ rolvaliduntil as "validUntil",
+ coalesce(r_config.role_configs, '{}') as config
+from
+ pg_roles r
+ left join (
+ select
+ oid,
+ jsonb_object_agg(param, value) filter (where param is not null) as role_configs
+ from
+ (
+ select
+ oid,
+ (string_to_array(unnest(rolconfig), '='))[1] as param,
+ (string_to_array(unnest(rolconfig), '='))[2] as value
+ from
+ pg_roles
+ ) as _
+ group by
+ oid
+ ) r_config on r_config.oid = r.oid
+`