Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
headers.append('Set-Cookie', serializeCookieHeader(name, value))
cookiesToSet.forEach(({ name, value, options }) =>
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
)
},
},
Expand Down Expand Up @@ -497,8 +497,8 @@ export async function action({ request }: ActionFunctionArgs) {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
headers.append('Set-Cookie', serializeCookieHeader(name, value))
cookiesToSet.forEach(({ name, value, options }) =>
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
)
},
},
Expand Down
12 changes: 6 additions & 6 deletions apps/docs/content/guides/realtime/authorization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ exists (
rooms_users
where
user_id = (select auth.uid())
and topic = (select realtime.topic())
and room_topic = (select realtime.topic())
and realtime.messages.extension in ('broadcast')
)
);
Expand Down Expand Up @@ -266,7 +266,7 @@ with check (
rooms_users
where
user_id = (select auth.uid())
and topic = (select realtime.topic())
and room_topic = (select realtime.topic())
and realtime.messages.extension in ('broadcast')
)
);
Expand All @@ -293,7 +293,7 @@ using (
rooms_users
where
user_id = (select auth.uid())
and topic = (select realtime.topic())
and room_topic = (select realtime.topic())
and realtime.messages.extension in ('presence')
)
);
Expand All @@ -316,7 +316,7 @@ with check (
rooms_users
where
user_id = (select auth.uid())
and name = (select realtime.topic())
and room_topic = (select realtime.topic())
and realtime.messages.extension in ('presence')
)
);
Expand All @@ -343,7 +343,7 @@ using (
rooms_users
where
user_id = (select auth.uid())
and topic = (select realtime.topic())
and room_topic = (select realtime.topic())
and realtime.messages.extension in ('broadcast', 'presence')
)
);
Expand All @@ -366,7 +366,7 @@ with check (
rooms_users
where
user_id = (select auth.uid())
and name = (select realtime.topic())
and room_topic = (select realtime.topic())
and realtime.messages.extension in ('broadcast', 'presence')
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,45 @@ To overcome these limitations and implement robust folder management with hierar
- **Implement RLS policies on `storage.objects`.** These policies must `JOIN` with your custom metadata table to enforce hierarchical access permissions based on your defined folder structure.
- **Handle batch folder operations via your metadata table.** For operations like moving or renaming folders, update the relevant entries in your custom metadata table. Note that actual file paths in Storage are not directly altered by these operations.
- **Optimize RLS policies for performance.** `JOIN`s in RLS policies can lead to performance degradation, especially with large datasets. Ensure proper indexing on your custom metadata table and consider using `SECURITY DEFINER` functions to optimize policy execution.

## Alternative approach: Using the S3 protocol for bulk operations

Supabase Storage also supports an S3-compatible API. This allows you to use tools like the AWS CLI to perform bulk file operations such as downloading, moving, or reorganizing objects more efficiently.

Install the AWS CLI by following the [AWS CLI installation guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).

Create S3 credentials in Supabase using the [Supabase S3 authentication guide](/docs/guides/storage/s3/authentication?queryGroups=language&language=credentials).

Configure an AWS CLI profile using the credentials you generated in Supabase. The profile name can be anything, but it must match the value used in the following commands.

```shell
aws configure --profile supabase-s3
```

Download files from a bucket or prefix:

```shell
aws s3 cp s3://bucket-name/folder-name ./download-target
--profile supabase-s3
--endpoint-url https://<project-ref>.supabase.co/storage/v1/s3
--recursive
--region <region>
```

- Replace `bucket-name` with your bucket name.
- Replace `folder-name` with the prefix you want to download, or omit it to download the entire bucket.
- Replace `<project-ref>` with your Supabase project reference.
- Replace `<region>` with your project's region (for example `eu-central-1`).
- `./download-target` is the local directory where files will be saved.

Move or rename files using the `mv` command. Because folders in Supabase Storage are implemented as prefixes, renaming a folder is effectively moving objects from one prefix to another.

```shell
aws s3 mv s3://bucket-name-one/folder-name-one s3://bucket-name-two/folder-name-two
--profile supabase-s3
--endpoint-url https://<project-ref>.supabase.co/storage/v1/s3
--recursive
--region <region>
```

This method is useful for large-scale downloads, migrations, or reorganizing files within a bucket.
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ const DATE_OPERATORS: OperatorDefinition[] = [
{ value: '<=', label: 'Less or equal', group: 'comparison' },
{ value: '=', label: 'Equals', group: 'comparison' },
{ value: '<>', label: 'Not equal', group: 'comparison' },
{ value: 'is', label: 'Is', group: 'setNull' },
]

const BOOLEAN_OPERATORS: OperatorDefinition[] = [
{ value: '=', label: 'Equals', group: 'comparison' },
{ value: '<>', label: 'Not equal', group: 'comparison' },
{ value: 'is', label: 'Is', group: 'setNull' },
]

export function columnToFilterProperty(column: SupaColumn): FilterProperty {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
'use client'

import { useMemo } from 'react'
import { Database } from 'lucide-react'
import { IS_PLATFORM } from 'common'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Auth, EdgeFunctions, Storage } from 'icons'
import { Database } from 'lucide-react'
import { useMemo } from 'react'
import type { ICommand } from 'ui-patterns/CommandMenu'
import {
CommandHeader,
CommandInput,
CommandWrapper,
PageType,
useQuery,
useRegisterCommands,
useRegisterPage,
useSetPage,
useQuery,
} from 'ui-patterns/CommandMenu'

import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { orderCommandSectionsByPriority } from './ordering'
import { ContextSearchResults } from './ContextSearchResults'
import { useFlag, IS_PLATFORM } from 'common'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { orderCommandSectionsByPriority } from './ordering'
import type { SearchContextValue } from './SearchContext.types'

interface SearchContextOption {
Expand Down Expand Up @@ -81,7 +82,6 @@ function ContextSearchPage({
}

export function useContextSearchCommands() {
const enableSearchEntitiesCommandMenu = useFlag('enableSearchEntitiesCommandMenu')
const { data: project } = useSelectedProjectQuery()
const setPage = useSetPage()

Expand Down Expand Up @@ -147,6 +147,6 @@ export function useContextSearchCommands() {
useRegisterCommands(COMMAND_MENU_SECTIONS.QUERY, contextCommands, {
orderSection: orderCommandSectionsByPriority,
sectionMeta: { priority: 3 },
enabled: !IS_PLATFORM || (enableSearchEntitiesCommandMenu && !!project),
enabled: !IS_PLATFORM || !!project,
})
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { IS_PLATFORM, useFlag } from 'common'
import { IS_PLATFORM } from 'common'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import {
Clock5,
Expand Down Expand Up @@ -51,7 +51,6 @@ const Graphql = dynamic(() => import('icons').then((mod) => mod.Graphql))
const CREATE_STUDIO_ENTITY = 'Create Studio Entity'

export function useCreateCommands(options?: CommandOptions) {
const enableCreateCommands = useFlag('enablecreatecommands')
const setIsOpen = useSetCommandMenuOpen()
const {
ref,
Expand Down Expand Up @@ -451,7 +450,7 @@ export function useCreateCommands(options?: CommandOptions) {
},
{
deps: [sections],
enabled: enableCreateCommands,
enabled: true,
}
)

Expand All @@ -469,7 +468,7 @@ export function useCreateCommands(options?: CommandOptions) {
...options,
orderSection: (sections) => sections,
sectionMeta: { priority: 3 },
enabled: enableCreateCommands,
enabled: true,
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const SSOConfig = () => {
plan="Team"
source="organizationSso"
primaryText="Organization Single Sign-on (SSO) is available from Team plan and above"
secondaryText="SSO as a login option provides additional acccount security for your team by enforcing the use of an identity provider when logging into Supabase. Upgrade to Team or above to set up SSO for your organization."
secondaryText="SSO as a login option provides additional account security for your team by enforcing the use of an identity provider when logging into Supabase. Upgrade to Team or above to set up SSO for your organization."
featureProposition="enable Single Sign-on (SSO)"
/>
) : isSuccess || isSSOProviderNotFound ? (
Expand Down
56 changes: 36 additions & 20 deletions apps/studio/components/interfaces/Reports/Reports.constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PlanId } from 'data/subscriptions/types'
import dayjs from 'dayjs'

import { PlanId } from 'data/subscriptions/types'
import type { DatetimeHelper } from '../Settings/Logs/Logs.types'
import { PresetConfig, Presets, ReportFilterItem } from './Reports.types'

Expand Down Expand Up @@ -400,11 +400,11 @@ select
-- mean_time,
coalesce(statements.rows::numeric / nullif(statements.calls, 0), 0) as avg_rows,
statements.rows as rows_read,
case
when (statements.shared_blks_hit + statements.shared_blks_read) > 0
case
when (statements.shared_blks_hit + statements.shared_blks_read) > 0
then round(
(statements.shared_blks_hit * 100.0) /
(statements.shared_blks_hit + statements.shared_blks_read),
(statements.shared_blks_hit * 100.0) /
(statements.shared_blks_hit + statements.shared_blks_read),
2
)
else 0
Expand All @@ -430,7 +430,8 @@ select
}
from pg_stat_statements as statements
inner join pg_authid as auth on statements.userid = auth.oid
${where || ''}
-- skip queries that were never actually executed
WHERE statements.calls > 0 ${where ? where.replace(/^WHERE/, 'AND') : ''}
${orderBy || 'order by statements.calls desc'}
limit 20`,
},
Expand All @@ -440,6 +441,11 @@ select
-- reports-query-performance-most-time-consuming
set search_path to public, extensions;

-- compute total time once up front so we don't need a window function over all rows
with grand_total as (
select coalesce(nullif(sum(total_exec_time + total_plan_time), 0), 1) as v
from pg_stat_statements where calls > 0
)
select
auth.rolname,
statements.query,
Expand All @@ -448,7 +454,7 @@ select
statements.mean_exec_time + statements.mean_plan_time as mean_time,
coalesce(
((statements.total_exec_time + statements.total_plan_time) /
nullif(sum(statements.total_exec_time + statements.total_plan_time) OVER(), 0)) *
(select v from grand_total)) *
100,
0
) as prop_total_time${
Expand All @@ -473,7 +479,8 @@ select
}
from pg_stat_statements as statements
inner join pg_authid as auth on statements.userid = auth.oid
${where || ''}
-- skip queries that were never actually executed
WHERE statements.calls > 0 ${where ? where.replace(/^WHERE/, 'AND') : ''}
${orderBy || 'order by total_time desc'}
limit 20`,
},
Expand Down Expand Up @@ -519,7 +526,8 @@ select
}
from pg_stat_statements as statements
inner join pg_authid as auth on statements.userid = auth.oid
${where || ''}
-- skip queries that were never actually executed
WHERE statements.calls > 0 ${where ? where.replace(/^WHERE/, 'AND') : ''}
${orderBy || 'order by max_time desc'}
limit 20`,
},
Expand All @@ -543,7 +551,12 @@ select
-- reports-query-performance-unified
set search_path to public, extensions;

with base as (
-- compute total time once up front so we don't need a window function over all rows
with grand_total as (
select coalesce(nullif(sum(total_exec_time + total_plan_time), 0), 1) as v
from pg_stat_statements where calls > 0
),
base as (
select
auth.rolname,
statements.query,
Expand All @@ -564,13 +577,14 @@ select
end as cache_hit_rate,
coalesce(
((statements.total_exec_time + statements.total_plan_time) /
nullif(sum(statements.total_exec_time + statements.total_plan_time) OVER(), 0)) *
(select v from grand_total)) *
100,
0
) as prop_total_time
from pg_stat_statements as statements
inner join pg_authid as auth on statements.userid = auth.oid
${where || ''}
-- skip queries that were never actually executed
WHERE statements.calls > 0 ${where ? where.replace(/^WHERE/, 'AND') : ''}
${orderBy || 'order by total_time desc'}
limit 50
),
Expand Down Expand Up @@ -615,28 +629,30 @@ select

-- Count of slow queries (> 1 second average)
SELECT count(*) as slow_queries_count
FROM pg_stat_statements
WHERE statements.mean_exec_time > 1000;`,
-- alias needed to reference columns in WHERE
FROM pg_stat_statements as statements
-- skip never-executed queries; mean_exec_time > 1000ms = avg over 1 second
WHERE statements.calls > 0 AND statements.mean_exec_time > 1000;`,
},
queryMetrics: {
queryType: 'db',
sql: (_params, where, orderBy, runIndexAdvisor = false, filterIndexAdvisor = false) => `
-- reports-query-performance-metrics
set search_path to public, extensions;
SELECT

SELECT
COALESCE(ROUND(AVG(statements.rows::numeric / NULLIF(statements.calls, 0)), 1), 0) as avg_rows_per_call,
COUNT(*) FILTER (WHERE statements.total_exec_time + statements.total_plan_time > 1000) as slow_queries,
COALESCE(
ROUND(
SUM(statements.shared_blks_hit) * 100.0 /
NULLIF(SUM(statements.shared_blks_hit + statements.shared_blks_read), 0),
SUM(statements.shared_blks_hit) * 100.0 /
NULLIF(SUM(statements.shared_blks_hit + statements.shared_blks_read), 0),
2
), 0
) || '%' as cache_hit_rate
FROM pg_stat_statements as statements
WHERE statements.calls > 0
${where || ''}
-- skip queries that were never actually executed
WHERE statements.calls > 0 ${where ? where.replace(/^WHERE/, 'AND') : ''}
${orderBy || ''}`,
},
},
Expand Down
Loading
Loading