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
10 changes: 9 additions & 1 deletion apps/docs/content/guides/ai/vector-indexes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ Indexes can be used to improve performance of nearest neighbor search using vari
| `<#>` | negative inner product | `vector_ip_ops` |
| `<=>` | cosine distance | `vector_cosine_ops` |

Currently vectors with up to 2,000 dimensions can be indexed.
For pgvector versions 0.7.0 and above, it's possible to create indexes on vectors with the following maximum dimensions:

- vector: up to 2,000 dimensions
- halfvec: up to 4,000 dimensions
- bit: up to 64,000 dimensions

You can check your current pgvector version by running: `SELECT * FROM pg_extension WHERE extname = 'vector';` or by navigating to the [Extensions](/dashboard/project/_/database/extensions) tab in your Supabase project dashboard.

If you are on an earlier version of pgvector, you should [upgrade your project here](/dashboard/project/_/settings/infrastructure).

## Resources

Expand Down
25 changes: 24 additions & 1 deletion apps/docs/content/guides/ai/vector-indexes/hnsw-indexes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,30 @@ create index on items using hnsw (column_name vector_ip_ops);
create index on items using hnsw (column_name vector_cosine_ops);
```

Currently vectors with up to 2,000 dimensions can be indexed.
For pgvector versions 0.7.0 and above, it's possible to create indexes on vectors with the following maximum dimensions:

- vector: up to 2,000 dimensions
- halfvec: up to 4,000 dimensions
- bit: up to 64,000 dimensions

You can check your current pgvector version by running: `SELECT * FROM pg_extension WHERE extname = 'vector';` or by navigating to the [Extensions](/dashboard/project/_/database/extensions) tab in your Supabase project dashboard.

If you are on an earlier version of pgvector, you should [upgrade your project here](/dashboard/project/_/settings/infrastructure).

## Example with high-dimensional vectors

For vectors with more than 2,000 dimensions, you can use the `halfvec` type to create indexes. Here's an example with 3,072 dimensions:

```sql
CREATE TABLE documents (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
content text,
embedding vector(3072)
);

CREATE INDEX ON documents
USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);
```

## How does HNSW work?

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/guides/platform/billing-faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Read more about [Compute usage](/docs/guides/platform/manage-your-usage/compute)

Egress refers to the total bandwidth (network traffic) quota available to each organization. This quota can be utilized for various purposes such as Storage, Realtime, Auth, Functions, Supavisor, Log Drains and Database. Each plan includes a specific egress quota, and any additional usage beyond that quota is billed accordingly.

We differentiate between cached (served via our CDN from cache hits) and uncached egress and give quotas for each type and have varying pricing (cached egress is cheaper).
We differentiate between cached (served via our CDN from cache hits) and uncached egress and give quotas for each type and have varying pricing (cached egress is cheaper). Cached egress only applies to Storage.

Read more about [Egress usage](/docs/guides/platform/manage-your-usage/egress).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ While pointing out the exact cause for egress may not be straightforward, there

**Cached vs uncached egress**

We differentiate between cached and uncached egress. Cached egress refers to egress that is served via our CDN and hits the cache. Uncached egress, on the other hand, refers to egress that is not served from the cache and requires a fresh request to the origin server.
We differentiate between cached and uncached egress. Cached egress, which only applies to Supabase Storage, refers to egress that is served via our CDN and hits the cache. Uncached egress, on the other hand, refers to egress that is not served from the cache and requires a fresh request to the origin server.

Your plan includes a quota for both cached and uncached egress and these are independent. Cached egress is also cheaper in case you exceed your quota.
77 changes: 77 additions & 0 deletions apps/studio/components/grid/MsSqlValidation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { isMsSqlForeignTable, type Entity } from 'data/table-editor/table-editor-types'
import type { ComponentType, ReactNode } from 'react'

import { Admonition } from 'ui-patterns'
import type { Filter, Sort } from './types'

type ValidateMsSqlSortingParams = {
filters: Filter[]
sorts: Sort[]
table: Entity
}

type MsSqlWarning = 'ConflictingSort' | 'NoValidSortPossible'

/**
* There is an edge case with the Postgres query planner and MS SQL foreign
* tables, where the Postgres query planner will drop sort clauses that are
* redundant with filters, resulting in invalid MS SQL syntax. We want to
* detect any conflicting or impossible sorts on filtered columns when the
* table is an MS SQL foreign table, and warn the user.
*/
export const validateMsSqlSorting = ({
filters,
sorts,
table,
}: ValidateMsSqlSortingParams):
| { warning: MsSqlWarning; Component: ComponentType }
| { warning: null } => {
const isMsSql = isMsSqlForeignTable(table)
if (!isMsSql) return { warning: null }

const equalityFilterColumns = new Set(
filters
.filter((filter) => filter.operator === '=' || filter.operator === 'is')
.map((filter) => filter.column)
)

const conflictingSort =
sorts.length > 0 && sorts.every((sort) => equalityFilterColumns.has(sort.column))
const showMsSqlSortWarning = equalityFilterColumns.size > 0 && !!conflictingSort
if (showMsSqlSortWarning)
return { warning: 'ConflictingSort', Component: MsSqlSortWarningAdmonition }

const noSortColumnsRemaining =
table.columns.length > 0 &&
table.columns.every((col) => col.data_type === 'json' || equalityFilterColumns.has(col.name))
const showMsSqlNoValidSortWarning = filters.length > 0 && noSortColumnsRemaining
if (showMsSqlNoValidSortWarning)
return { warning: 'NoValidSortPossible', Component: MsSqlNoValidSortAdmonition }

return { warning: null }
}

type MsSqlAdmonitionProps = {
title: string
children: string
}

const MsSqlAdmonition = ({ title, children }: MsSqlAdmonitionProps): ReactNode => (
<div className="mt-2 px-3 pb-2">
<Admonition type="warning" title={title} description={children} />
</div>
)

const MsSqlSortWarningAdmonition = (): ReactNode => (
<MsSqlAdmonition title="Cannot sort by filtered column">
Sorting only by columns filtered with "=" or "is" doesn't work on MSSQL tables. Pick a different
sorting column, or add a column not in your filter.
</MsSqlAdmonition>
)

const MsSqlNoValidSortAdmonition = (): ReactNode => (
<MsSqlAdmonition title="No valid sort column remaining">
All columns that can be sorted have been filtered with "=" or "is", which doesn't work on MSSQL
tables. Remove a column from your filter to continue.
</MsSqlAdmonition>
)
19 changes: 17 additions & 2 deletions apps/studio/components/grid/SupabaseGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend'
import { createPortal } from 'react-dom'

import { useParams } from 'common'
import { isMsSqlForeignTable } from 'data/table-editor/table-editor-types'
import { useTableRowsQuery } from 'data/table-rows/table-rows-query'
import { RoleImpersonationState } from 'lib/role-impersonation'
import { EMPTY_ARR } from 'lib/void'
Expand All @@ -22,6 +23,7 @@ import { GridProps } from './types'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useTableFilter } from './hooks/useTableFilter'
import { useTableSort } from './hooks/useTableSort'
import { validateMsSqlSorting } from './MsSqlValidation'

export const SupabaseGrid = ({
customHeader,
Expand All @@ -47,6 +49,11 @@ export const SupabaseGrid = ({

const roleImpersonationState = useRoleImpersonationStateSnapshot()

const msSqlWarning = isMsSqlForeignTable(snap.originalTable)
? validateMsSqlSorting({ filters, sorts, table: snap.originalTable })
: { warning: null }
const tableQueriesEnabled = msSqlWarning.warning === null

const { data, error, isSuccess, isError, isLoading, isRefetching } = useTableRowsQuery(
{
projectRef: project?.ref,
Expand All @@ -60,6 +67,7 @@ export const SupabaseGrid = ({
},
{
keepPreviousData: true,
enabled: tableQueriesEnabled,
retry: (_, error: any) => {
const doesNotExistError = error && error.message?.includes('does not exist')
if (doesNotExistError) onApplySorts([])
Expand All @@ -77,7 +85,13 @@ export const SupabaseGrid = ({
return (
<DndProvider backend={HTML5Backend} context={window}>
<div className="sb-grid h-full flex flex-col">
<Header customHeader={customHeader} isRefetching={isRefetching} />
<Header
customHeader={customHeader}
isRefetching={isRefetching}
tableQueriesEnabled={tableQueriesEnabled}
/>

{msSqlWarning.warning !== null && <msSqlWarning.Component />}

{children || (
<>
Expand All @@ -86,13 +100,14 @@ export const SupabaseGrid = ({
{...gridProps}
rows={rows}
error={error}
isDisabled={!tableQueriesEnabled}
isLoading={isLoading}
isSuccess={isSuccess}
isError={isError}
filters={filters}
onApplyFilters={onApplyFilters}
/>
<Footer />
<Footer enableForeignRowsQuery={tableQueriesEnabled} />
<Shortcuts gridRef={gridRef} rows={rows} />
</>
)}
Expand Down
8 changes: 6 additions & 2 deletions apps/studio/components/grid/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useUrlState } from 'hooks/ui/useUrlState'
import { Pagination } from './pagination/Pagination'

export const Footer = () => {
type FooterProps = {
enableForeignRowsQuery?: boolean
}

export const Footer: React.FC<FooterProps> = ({ enableForeignRowsQuery = true }: FooterProps) => {
const { id: _id } = useParams()
const id = _id ? Number(_id) : undefined
const { data: project } = useSelectedProjectQuery()
Expand All @@ -33,7 +37,7 @@ export const Footer = () => {

return (
<GridFooter>
{selectedView === 'data' && <Pagination />}
{selectedView === 'data' && <Pagination enableForeignRowsQuery={enableForeignRowsQuery} />}

<div className="ml-auto flex items-center gap-x-2">
{(isViewSelected || isTableSelected) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ const RowCountSelector = ({
)
}

export const Pagination = () => {
type PaginationProps = {
enableForeignRowsQuery?: boolean
}

export const Pagination = ({ enableForeignRowsQuery = true }: PaginationProps) => {
const { id: _id } = useParams()
const id = _id ? Number(_id) : undefined

Expand Down Expand Up @@ -108,7 +112,7 @@ export const Pagination = () => {
roleImpersonationState: roleImpersonationState as RoleImpersonationState,
},
{
enabled: isForeignTableSelected,
enabled: isForeignTableSelected && enableForeignRowsQuery,
}
)
const isLastPage = (rowsData?.rows ?? []).length < tableEditorSnap.rowsPerPage
Expand Down Expand Up @@ -223,8 +227,8 @@ export const Pagination = () => {
icon={<ArrowRight />}
type="outline"
className="px-1.5"
disabled={isLastPage}
loading={isLoadingRows}
disabled={isLastPage || !enableForeignRowsQuery}
loading={isLoadingRows && enableForeignRowsQuery}
onClick={goToNextPage}
/>
<RowCountSelector onRowsPerPageChange={onRowsPerPageChange} />
Expand Down
4 changes: 3 additions & 1 deletion apps/studio/components/grid/components/grid/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const rowKeyGetter = (row: SupaRow) => {
interface IGrid extends GridProps {
rows: any[]
error: any
isDisabled?: boolean
isLoading: boolean
isSuccess: boolean
isError: boolean
Expand All @@ -45,6 +46,7 @@ export const Grid = memo(
rowClass,
rows,
error,
isDisabled = false,
isLoading,
isSuccess,
isError,
Expand Down Expand Up @@ -154,7 +156,7 @@ export const Grid = memo(
onDragLeave={onDragOver}
onDrop={onFileDrop}
>
{isLoading && <GenericSkeletonLoader />}
{isLoading && !isDisabled && <GenericSkeletonLoader />}

{isError && <GridError error={error} />}

Expand Down
37 changes: 23 additions & 14 deletions apps/studio/components/grid/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ export const MAX_EXPORT_ROW_COUNT_MESSAGE = (
export type HeaderProps = {
customHeader: ReactNode
isRefetching: boolean
tableQueriesEnabled?: boolean
}

export const Header = ({ customHeader, isRefetching }: HeaderProps) => {
export const Header = ({ customHeader, isRefetching, tableQueriesEnabled = true }: HeaderProps) => {
const snap = useTableEditorTableStateSnapshot()

return (
Expand All @@ -69,7 +70,7 @@ export const Header = ({ customHeader, isRefetching }: HeaderProps) => {
{customHeader ? (
customHeader
) : snap.selectedRows.size > 0 ? (
<RowHeader />
<RowHeader tableQueriesEnabled={tableQueriesEnabled} />
) : (
<DefaultHeader />
)}
Expand Down Expand Up @@ -224,7 +225,12 @@ const DefaultHeader = () => {
)
}

const RowHeader = () => {
type RowHeaderProps = {
tableQueriesEnabled?: boolean
}

const RowHeader = ({ tableQueriesEnabled = true }: RowHeaderProps) => {
debugger
const { data: project } = useSelectedProjectQuery()
const tableEditorSnap = useTableEditorStateSnapshot()
const snap = useTableEditorTableStateSnapshot()
Expand All @@ -238,16 +244,19 @@ const RowHeader = () => {
const [isExporting, setIsExporting] = useState(false)
const [showExportModal, setShowExportModal] = useState(false)

const { data } = useTableRowsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
tableId: snap.table.id,
sorts,
filters,
page: snap.page,
limit: tableEditorSnap.rowsPerPage,
roleImpersonationState: roleImpersonationState as RoleImpersonationState,
})
const { data } = useTableRowsQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
tableId: snap.table.id,
sorts,
filters,
page: snap.page,
limit: tableEditorSnap.rowsPerPage,
roleImpersonationState: roleImpersonationState as RoleImpersonationState,
},
{ enabled: tableQueriesEnabled }
)

const { data: countData } = useTableRowsCountQuery(
{
Expand All @@ -258,7 +267,7 @@ const RowHeader = () => {
enforceExactCount: snap.enforceExactCount,
roleImpersonationState: roleImpersonationState as RoleImpersonationState,
},
{ keepPreviousData: true }
{ keepPreviousData: true, enabled: tableQueriesEnabled }
)

const allRows = data?.rows ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ export const PolicyEditorPanel = memo(function ({

if (selectedPolicy.command === 'INSERT') {
// [Joshen] Cause editorOneRef will be the check statement in this scenario
if (selectedPolicy.check !== null && selectedPolicy.check !== using) payload.check = using
if (selectedPolicy.check !== using) payload.check = using
} else {
if (selectedPolicy.check !== null && selectedPolicy.check !== check) payload.check = check
if (selectedPolicy.check !== check) payload.check = check
}

if (Object.keys(payload).length === 0) return onSelectCancel()
Expand Down
Loading
Loading