Repository: supabase/supabase
Pull request #35532: fix: move column create request to query route
Author: login: sweatybridge
Created: 2025-05-07T16:08:31Z | Merged: 2025-05-14T05:23:31Z

## What kind of change does this PR introduce?

Bug fix

## What is the new behavior?

- escapes `default_value` and `check` properly
- mirrors pgmeta [implementation](https://github.com/supabase/postgres-meta/blob/5068458c86c10800d106d81471d7ccb4350d3653/src/lib/PostgresMetaColumns.ts#L152) exactly

## Additional context

Add any other context or screenshots.



Diff:
diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
index 408f3ecce1f5e..b5f3f59a59b08 100644
--- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
+++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
@@ -173,7 +173,7 @@ const ColumnEditor = ({
 
       if (isEmpty(errors)) {
         const payload = isNewRecord
-          ? generateCreateColumnPayload(selectedTable.id, columnFields)
+          ? generateCreateColumnPayload(selectedTable, columnFields)
           : generateUpdateColumnPayload(column!, selectedTable, columnFields)
         const configuration = {
           columnId: column?.id,
diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts
index f50842258c685..ed670017759ed 100644
--- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts
+++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts
@@ -84,13 +84,14 @@ export const generateColumnFieldFromPostgresColumn = (
 }
 
 export const generateCreateColumnPayload = (
-  tableId: number,
+  table: PostgresTable,
   field: ColumnField
 ): CreateColumnPayload => {
   const isIdentity = field.format.includes('int') ? field.isIdentity : false
-  const defaultValue = field.defaultValue as any
+  const defaultValue = field.defaultValue
   const payload: CreateColumnPayload = {
-    tableId,
+    schema: table.schema,
+    table: table.name,
     isIdentity,
     name: field.name.trim(),
     comment: field.comment?.trim(),
@@ -106,9 +107,7 @@ export const generateCreateColumnPayload = (
     ...(!isIdentity &&
       defaultValue && {
         defaultValueFormat:
-          isNull(defaultValue) || isSQLExpression(defaultValue)
-            ? 'expression'
-            : ('literal' as 'expression' | 'literal'),
+          isNull(defaultValue) || isSQLExpression(defaultValue) ? 'expression' : 'literal',
       }),
   }
   return payload
diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx
index c15f39c0fd47c..c47098b4afcb3 100644
--- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx
+++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx
@@ -230,7 +230,7 @@ export const createColumn = async ({
   try {
     // Once pg-meta supports composite keys, we can remove this logic
     const { isPrimaryKey, ...formattedPayload } = payload
-    const column = await createDatabaseColumn({
+    await createDatabaseColumn({
       projectRef: projectRef,
       connectionString: connectionString,
       payload: formattedPayload,
@@ -246,18 +246,18 @@ export const createColumn = async ({
         await dropConstraint(
           projectRef,
           connectionString,
-          column.schema,
-          column.table,
+          payload.schema,
+          payload.table,
           primaryKey.name
         )
       }
 
-      const primaryKeyColumns = existingPrimaryKeys.concat([column.name])
+      const primaryKeyColumns = existingPrimaryKeys.concat([formattedPayload.name])
       await addPrimaryKey(
         projectRef,
         connectionString,
-        column.schema,
-        column.table,
+        payload.schema,
+        payload.table,
         primaryKeyColumns
       )
     }
@@ -267,13 +267,13 @@ export const createColumn = async ({
       await addForeignKey({
         projectRef,
         connectionString,
-        table: { schema: column.schema, name: column.table },
+        table: { schema: payload.schema, name: payload.table },
         foreignKeys: foreignKeyRelations,
       })
     }
 
     if (!skipSuccessMessage) {
-      toast.success(`Successfully created column "${column.name}"`, { id: toastId })
+      toast.success(`Successfully created column "${formattedPayload.name}"`, { id: toastId })
     }
     return { error: undefined }
   } catch (error: any) {
@@ -496,7 +496,7 @@ export const createTable = async ({
 
     for (const column of columns) {
       // We create all columns without primary keys first
-      const columnPayload = generateCreateColumnPayload(table.id, {
+      const columnPayload = generateCreateColumnPayload(table, {
         ...column,
         isPrimaryKey: false,
       })
@@ -699,7 +699,7 @@ export const updateTable = async ({
       toast.loading(`Adding column ${column.name} to ${updatedTable.name}`, { id: toastId })
       // Ensure that columns do not created as primary key first, cause the primary key will
       // be added later on further down in the code
-      const columnPayload = generateCreateColumnPayload(updatedTable.id, {
+      const columnPayload = generateCreateColumnPayload(updatedTable, {
         ...column,
         isPrimaryKey: false,
       })
diff --git a/apps/studio/data/database-columns/database-column-create-mutation.ts b/apps/studio/data/database-columns/database-column-create-mutation.ts
index 0a96c279bba3c..74bc316d5d3d7 100644
--- a/apps/studio/data/database-columns/database-column-create-mutation.ts
+++ b/apps/studio/data/database-columns/database-column-create-mutation.ts
@@ -1,11 +1,15 @@
+import pgMeta from '@supabase/pg-meta'
 import { useMutation, UseMutationOptions } from '@tanstack/react-query'
 import { toast } from 'sonner'
 
 import type { components } from 'data/api'
-import { handleError, post } from 'data/fetchers'
+import { executeSql } from 'data/sql/execute-sql-query'
 import type { ResponseError } from 'types'
 
-export type CreateColumnBody = components['schemas']['CreateColumnBody']
+export type CreateColumnBody = Omit<components['schemas']['CreateColumnBody'], 'tableId'> & {
+  schema: string
+  table: string
+}
 
 export type DatabaseColumnCreateVariables = {
   projectRef: string
@@ -18,20 +22,30 @@ export async function createDatabaseColumn({
   connectionString,
   payload,
 }: DatabaseColumnCreateVariables) {
-  let headers = new Headers()
-  if (connectionString) headers.set('x-connection-encrypted', connectionString)
-
-  const { data, error } = await post('/platform/pg-meta/{ref}/columns', {
-    params: {
-      header: { 'x-connection-encrypted': connectionString! },
-      path: { ref: projectRef },
-    },
-    body: payload,
-    headers,
+  const { sql } = pgMeta.columns.create({
+    schema: payload.schema,
+    table: payload.table,
+    name: payload.name,
+    type: payload.type,
+    default_value: payload.defaultValue,
+    default_value_format: payload.defaultValueFormat,
+    is_identity: payload.isIdentity,
+    identity_generation: payload.identityGeneration,
+    is_nullable: payload.isNullable,
+    is_primary_key: payload.isPrimaryKey,
+    is_unique: payload.isUnique,
+    comment: payload.comment,
+    check: payload.check,
+  })
+
+  const { result } = await executeSql({
+    projectRef,
+    connectionString,
+    sql,
+    queryKey: ['column', 'create'],
   })
 
-  if (error) handleError(error)
-  return data
+  return result
 }
 
 type DatabaseColumnCreateData = Awaited<ReturnType<typeof createDatabaseColumn>>
diff --git a/packages/pg-meta/src/pg-meta-columns.ts b/packages/pg-meta/src/pg-meta-columns.ts
index 1be182cbb8a3a..aaedd84dd2220 100644
--- a/packages/pg-meta/src/pg-meta-columns.ts
+++ b/packages/pg-meta/src/pg-meta-columns.ts
@@ -83,13 +83,7 @@ where
   }
 }
 
-type ColumnIdentifier =
-  | Pick<PGColumn, 'id'>
-  | {
-      schema: string
-      table: string
-      name: string
-    }
+type ColumnIdentifier = Pick<PGColumn, 'id'> | Pick<PGColumn, 'name' | 'schema' | 'table'>
 
 function getIdentifierWhereClause(identifier: ColumnIdentifier) {
   if ('id' in identifier && identifier.id) {
@@ -112,7 +106,8 @@ function retrieve(identifier: ColumnIdentifier): {
 }
 
 function create({
-  table_id,
+  schema,
+  table,
   name,
   type,
   default_value,
@@ -125,7 +120,8 @@ function create({
   comment,
   check,
 }: {
-  table_id: number
+  schema: string
+  table: string
   name: string
   type: string
   default_value?: any
@@ -143,50 +139,53 @@ function create({
     if (default_value !== undefined) {
       throw new Error('Columns cannot both be identity and have a default value')
     }
+
     defaultValueClause = `GENERATED ${identity_generation} AS IDENTITY`
-  } else if (default_value !== undefined) {
-    const formattedDefault =
-      // Here we must wrap the litteral value into a String in case it receive a direct number to quote and escape
-      // for the execute(format()) call. Maybe we should change the `default_value` to only accept a string instead ?
-      default_value_format === 'expression' ? default_value : `'${literal(String(default_value))}'`
-    defaultValueClause = `DEFAULT ${formattedDefault}`
+  } else {
+    if (default_value === undefined) {
+      // skip
+    } else if (default_value_format === 'expression') {
+      defaultValueClause = `DEFAULT ${default_value}`
+    } else {
+      defaultValueClause = `DEFAULT ${literal(default_value)}`
+    }
   }
 
-  const constraints: string[] = []
-  if (is_nullable === false) constraints.push('NOT NULL')
-  if (is_primary_key) constraints.push('PRIMARY KEY')
-  if (is_unique) constraints.push('UNIQUE')
-  if (check) constraints.push(`CHECK (${check})`)
+  let isNullableClause = ''
+  if (is_nullable !== undefined) {
+    isNullableClause = is_nullable ? 'NULL' : 'NOT NULL'
+  }
+  const isPrimaryKeyClause = is_primary_key ? 'PRIMARY KEY' : ''
+  const isUniqueClause = is_unique ? 'UNIQUE' : ''
+  const checkSql = check === undefined ? '' : `CHECK (${check})`
+  const commentSql =
+    comment === undefined
+      ? ''
+      : `COMMENT ON COLUMN ${ident(schema)}.${ident(table)}.${ident(name)} IS ${literal(comment)}`
 
   const sql = `
-DO $$
-DECLARE
-  v_schema name;
-  v_table name;
-BEGIN
-  SELECT n.nspname, c.relname INTO v_schema, v_table
-  FROM pg_class c 
-  JOIN pg_namespace n ON n.oid = c.relnamespace
-  WHERE c.oid = ${literal(table_id)};
-
-  IF v_schema IS NULL THEN
-    RAISE EXCEPTION 'Table with id % not found', ${literal(table_id)};
-  END IF;
-
-  execute(format(
-    'ALTER TABLE %I.%I ADD COLUMN %I %s ${defaultValueClause} ${constraints.join(' ')}',
-    v_schema,
-    v_table,
-    ${literal(name)},
-    ${literal(type)}
-  ));
-  
-  ${comment ? `execute(format('COMMENT ON COLUMN %I.%I.%I IS %L', v_schema, v_table, ${literal(name)}, quote_ident(${literal(comment)})));` : ''}
-END $$;`
+BEGIN;
+  ALTER TABLE ${ident(schema)}.${ident(table)} ADD COLUMN ${ident(name)} ${typeIdent(type)}
+    ${defaultValueClause}
+    ${isNullableClause}
+    ${isPrimaryKeyClause}
+    ${isUniqueClause}
+    ${checkSql};
+  ${commentSql};
+COMMIT;`
 
   return { sql }
 }
 
+// TODO: make this more robust - use type_id or type_schema + type_name instead of just type.
+const typeIdent = (type: string) => {
+  return type.endsWith('[]')
+    ? `${ident(type.slice(0, -2))}[]`
+    : type.includes('.')
+      ? type
+      : ident(type)
+}
+
 function update(
   id: string,
   {
diff --git a/packages/pg-meta/test/columns.test.ts b/packages/pg-meta/test/columns.test.ts
index f2088abb7ea8f..3b524386408b1 100644
--- a/packages/pg-meta/test/columns.test.ts
+++ b/packages/pg-meta/test/columns.test.ts
@@ -1,4 +1,4 @@
-import { test, beforeAll, afterAll, expect } from 'vitest'
+import { test, beforeAll, afterAll, expect, describe } from 'vitest'
 import pgMeta from '../src/index'
 import { createTestDatabase, cleanupRoot } from './db/utils'
 
@@ -178,18 +178,10 @@ withTestDatabase('retrieve, create, update, delete column', async ({ executeQuer
   // Create test table using pure SQL
   await executeQuery('CREATE TABLE t ()')
 
-  // Get table ID
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
-
   // Create column
   const { sql: createColumnSql } = await pgMeta.columns.create({
-    table_id: tableId,
+    schema: 'public',
+    table: 't',
     name: 'c',
     type: 'int2',
     default_value: 42,
@@ -214,7 +206,7 @@ withTestDatabase('retrieve, create, update, delete column', async ({ executeQuer
       "check": null,
       "comment": "foo",
       "data_type": "smallint",
-      "default_value": "'42'::smallint",
+      "default_value": "42",
       "enums": [],
       "format": "int2",
       "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/,
@@ -337,18 +329,10 @@ withTestDatabase('primary key column', async ({ executeQuery }) => {
   // Create test table using pure SQL
   await executeQuery('CREATE TABLE t ()')
 
-  // Get table ID
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
-
   // Create column with primary key
   const { sql: createColumnSql } = await pgMeta.columns.create({
-    table_id: tableId,
+    schema: 'public',
+    table: 't',
     name: 'c',
     type: 'int2',
     is_primary_key: true,
@@ -378,18 +362,10 @@ withTestDatabase('unique column', async ({ executeQuery }) => {
   // Create test table using pure SQL
   await executeQuery('CREATE TABLE t ()')
 
-  // Get table ID
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
-
   // Create column with unique constraint
   const { sql: createColumnSql } = await pgMeta.columns.create({
-    table_id: tableId,
+    schema: 'public',
+    table: 't',
     name: 'c',
     type: 'int2',
     is_unique: true,
@@ -416,124 +392,178 @@ withTestDatabase('unique column', async ({ executeQuery }) => {
   `)
 })
 
-withTestDatabase('array column', async ({ executeQuery }) => {
-  // Create test table using pure SQL
-  await executeQuery('CREATE TABLE t ()')
+describe('array column', async () => {
+  const db = await createTestDatabase()
+  await db.executeQuery('CREATE TABLE t ()')
 
-  // Get table ID
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
-
-  // Create array column
-  const { sql: createColumnSql } = await pgMeta.columns.create({
-    table_id: tableId,
-    name: 'c',
-    type: 'int2[]',
+  afterAll(async () => {
+    await db.cleanup()
   })
-  await executeQuery(createColumnSql)
 
-  // Retrieve and verify the created column
-  const { sql: retrieveSql, zod: retrieveZod } = await pgMeta.columns.retrieve({
-    schema: 'public',
-    table: 't',
-    name: 'c',
-  })
-  const column = retrieveZod.parse((await executeQuery(retrieveSql))[0])
-
-  expect(column).toMatchInlineSnapshot(
-    {
-      id: expect.stringMatching(/^\d+\.1$/),
+  test.concurrent.for([
+    // numerical types
+    { type: 'int2[]', etype: '_int2' },
+    { type: 'int4[]', etype: '_int4' },
+    { type: 'int8[]', etype: '_int8' },
+    { type: 'float4[]', etype: '_float4' },
+    { type: 'float8[]', etype: '_float8' },
+    { type: 'numeric[]', etype: '_numeric' },
+    // json types
+    { type: 'json[]', etype: '_json' },
+    { type: 'jsonb[]', etype: '_jsonb' },
+    // text types
+    { type: 'text[]', etype: '_text' },
+    { type: 'varchar[]', etype: '_varchar' },
+    // datetime types
+    { type: 'timestamp[]', etype: '_timestamp' },
+    { type: 'timestamptz[]', etype: '_timestamptz' },
+    { type: 'date[]', etype: '_date' },
+    { type: 'time[]', etype: '_time' },
+    { type: 'timetz[]', etype: '_timetz' },
+    // other types
+    { type: 'uuid[]', etype: '_uuid' },
+    { type: 'bool[]', etype: '_bool' },
+    { type: 'bytea[]', etype: '_bytea' },
+  ])('$type -> $etype', async (c, { expect, task }) => {
+    const id = { schema: 'public', table: 't', name: `c${task.id}` }
+
+    // Create column with default value
+    const { sql } = pgMeta.columns.create({
+      ...id,
+      type: c.type,
+      default_value: null,
+    })
+    await db.executeQuery(sql)
+
+    // Retrieve and verify the created column
+    const expected = pgMeta.columns.retrieve(id)
+    const result = await db.executeQuery(expected.sql)
+    const column = expected.zod.parse(result[0])
+
+    expect(column).toStrictEqual({
+      ...id,
+      data_type: 'ARRAY',
+      default_value: null,
+      format: c.etype,
+      id: expect.stringMatching(/^\d+\.\d+$/),
+      ordinal_position: expect.any(Number),
       table_id: expect.any(Number),
-    },
-    `
-    {
-      "check": null,
-      "comment": null,
-      "data_type": "ARRAY",
-      "default_value": null,
-      "enums": [],
-      "format": "_int2",
-      "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/,
-      "identity_generation": null,
-      "is_generated": false,
-      "is_identity": false,
-      "is_nullable": true,
-      "is_unique": false,
-      "is_updatable": true,
-      "name": "c",
-      "ordinal_position": 1,
-      "schema": "public",
-      "table": "t",
-      "table_id": Any<Number>,
-    }
-  `
-  )
+      check: null,
+      comment: null,
+      enums: [],
+      identity_generation: null,
+      is_generated: false,
+      is_identity: false,
+      is_nullable: true,
+      is_unique: false,
+      is_updatable: true,
+    })
+  })
 })
 
-withTestDatabase('column with default value', async ({ executeQuery }) => {
-  // Create test table using pure SQL
-  await executeQuery('CREATE TABLE t ()')
-
-  // Get table ID
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
+describe('column with default value', async () => {
+  const db = await createTestDatabase()
+  await db.executeQuery('CREATE TABLE t ()')
 
-  // Create column with default value
-  const { sql: createColumnSql } = await pgMeta.columns.create({
-    table_id: tableId,
-    name: 'c',
-    type: 'timestamptz',
-    default_value: 'NOW()',
-    default_value_format: 'expression',
+  afterAll(async () => {
+    await db.cleanup()
   })
-  await executeQuery(createColumnSql)
 
-  // Retrieve and verify the created column
-  const { sql: retrieveSql, zod: retrieveZod } = await pgMeta.columns.retrieve({
-    schema: 'public',
-    table: 't',
-    name: 'c',
-  })
-  const column = retrieveZod.parse((await executeQuery(retrieveSql))[0])
-
-  expect(column).toMatchInlineSnapshot(
+  test.concurrent.for([
+    // numerical types
+    { type: 'int2', value: 0, etype: 'smallint', evalue: '0' },
+    { type: 'int4', value: 1, etype: 'integer', evalue: '1' },
+    { type: 'int8', value: -1, etype: 'bigint', evalue: `'-1'::integer` },
+    { type: 'float4', value: 0.1, etype: 'real', evalue: '0.1' },
+    { type: 'float8', value: -0.1, etype: 'double precision', evalue: `'-0.1'::numeric` },
+    { type: 'numeric', value: 1e2, etype: 'numeric', evalue: '100' },
+    // json types
     {
-      id: expect.stringMatching(/^\d+\.1$/),
-      table_id: expect.any(Number),
+      type: 'json',
+      value: { a: 0, b: '1', c: true },
+      etype: 'json',
+      evalue: `'{\"a\": 0, \"b\": \"1\", \"c\": true}'::jsonb`,
     },
-    `
+    // json array must be stringified, otherwise it will be converted to pg array literal
+    { type: 'jsonb', value: JSON.stringify([null]), etype: 'jsonb', evalue: `'[null]'::jsonb` },
+    // text types
+    { type: 'text', value: `quote's`, etype: 'text', evalue: `'quote''s'::text` },
+    { type: 'varchar', value: '\n', etype: 'character varying', evalue: `'\n'::character varying` },
+    // datetime types
     {
-      "check": null,
-      "comment": null,
-      "data_type": "timestamp with time zone",
-      "default_value": "now()",
-      "enums": [],
-      "format": "timestamptz",
-      "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/,
-      "identity_generation": null,
-      "is_generated": false,
-      "is_identity": false,
-      "is_nullable": true,
-      "is_unique": false,
-      "is_updatable": true,
-      "name": "c",
-      "ordinal_position": 1,
-      "schema": "public",
-      "table": "t",
-      "table_id": Any<Number>,
-    }
-  `
-  )
+      type: 'timestamp',
+      value: `now() - INTERVAL '1 day'`,
+      etype: 'timestamp without time zone',
+      evalue: `(now() - '1 day'::interval)`,
+      exp: true,
+    },
+    {
+      type: 'timestamptz',
+      value: 'NOW()',
+      etype: 'timestamp with time zone',
+      evalue: 'now()',
+      exp: true,
+    },
+    { type: 'date', value: '2025-05-09', etype: 'date', evalue: `'2025-05-09'::date` },
+    {
+      type: 'time',
+      value: '11:22:33',
+      etype: 'time without time zone',
+      evalue: `'11:22:33'::time without time zone`,
+    },
+    {
+      type: 'timetz',
+      value: '11:22:33+0800',
+      etype: 'time with time zone',
+      evalue: `'11:22:33+08'::time with time zone`,
+    },
+    // other types
+    {
+      type: 'uuid',
+      value: 'gen_random_uuid()',
+      etype: 'uuid',
+      evalue: 'gen_random_uuid()',
+      exp: true,
+    },
+    { type: 'bool', value: true, etype: 'boolean', evalue: 'true' },
+    // https://www.postgresql.org/docs/current/datatype-binary.html#DATATYPE-BINARY-BYTEA-ESCAPE-FORMAT
+    { type: 'bytea', value: `\\000`, etype: 'bytea', evalue: `'\\x00'::bytea` },
+  ])('$type -> $value', async (c, { expect, task }) => {
+    const id = { schema: 'public', table: 't', name: `c${task.id}` }
+
+    // Create column with default value
+    const { sql } = pgMeta.columns.create({
+      ...id,
+      type: c.type,
+      default_value: c.value,
+      default_value_format: c.exp ? 'expression' : 'literal',
+    })
+    await db.executeQuery(sql)
+
+    // Retrieve and verify the created column
+    const expected = pgMeta.columns.retrieve(id)
+    const result = await db.executeQuery(expected.sql)
+    const column = expected.zod.parse(result[0])
+
+    expect(column).toStrictEqual({
+      ...id,
+      data_type: c.etype,
+      default_value: c.evalue,
+      format: c.type,
+      id: expect.stringMatching(/^\d+\.\d+$/),
+      ordinal_position: expect.any(Number),
+      table_id: expect.any(Number),
+      check: null,
+      comment: null,
+      enums: [],
+      identity_generation: null,
+      is_generated: false,
+      is_identity: false,
+      is_nullable: true,
+      is_unique: false,
+      is_updatable: true,
+    })
+  })
 })
 
 // https://github.com/supabase/supabase/issues/3553
@@ -542,18 +572,10 @@ withTestDatabase('alter column to type with uppercase', async ({ executeQuery })
   await executeQuery('CREATE TABLE t ()')
   await executeQuery('CREATE TYPE "T" AS ENUM ()')
 
-  // Get table ID
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
-
   // Create and then update column
   const { sql: createSql } = await pgMeta.columns.create({
-    table_id: tableId,
+    schema: 'public',
+    table: 't',
     name: 'c',
     type: 'text',
     is_unique: false,
@@ -615,18 +637,10 @@ withTestDatabase('enums are populated in enum array columns', async ({ executeQu
   await executeQuery(`CREATE TYPE test_enum AS ENUM ('a')`)
   await executeQuery('CREATE TABLE t ()')
 
-  // Get table ID
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
-
   // Create column
   const { sql: createSql } = await pgMeta.columns.create({
-    table_id: tableId,
+    schema: 'public',
+    table: 't',
     name: 'c',
     type: '_test_enum',
   })
@@ -819,18 +833,10 @@ withTestDatabase('column with fully-qualified type', async ({ executeQuery }) =>
     create type s.my_type as enum ();
   `)
 
-  // Get table ID using pure SQL
-  const tableId = Number(
-    (
-      await executeQuery(
-        "SELECT oid FROM pg_class WHERE relname = 't' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')"
-      )
-    )[0].oid
-  )
-
   // Create column with fully-qualified type
   const { sql: createColumnSql } = await pgMeta.columns.create({
-    table_id: tableId,
+    schema: 'public',
+    table: 't',
     name: 'c',
     type: 's.my_type',
   })


Reviews:
user: login: sweatybridge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T16:17:04Z
user: login: jgoux
body: Green tests for the added code, other tests are flaky ones. 👍 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T12:29:42Z
user: login: avallete
body: LGTM

Wondering if it might be wise to fork the logic for now and "hide" this behind a feature flag for now instead of replacing current behavior ? It would allow for gradual rollout of the refactor and easier rollback in case it break the users dashboard. We can start by rolling this out for all `@supabase.io` users per default.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T07:54:49Z
user: login: sweatybridge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T15:47:42Z
user: login: avallete
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T07:40:33Z
user: login: avallete
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T07:47:11Z
user: login: alaister
body: LGTM!

- Tested on preview
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T04:13:31Z

Review comments:
diff_hunk: @@ -138,25 +138,22 @@ function create({
   comment?: string
   check?: string
 }): { sql: string } {
-  let defaultValueClause = ''
+  const clauses = [literal(type)]
   if (is_identity) {
     if (default_value !== undefined) {
       throw new Error('Columns cannot both be identity and have a default value')
     }
-    defaultValueClause = `GENERATED ${identity_generation} AS IDENTITY`
+    clauses.push(`GENERATED ${identity_generation} AS IDENTITY`)
   } else if (default_value !== undefined) {
     const formattedDefault =
-      // Here we must wrap the litteral value into a String in case it receive a direct number to quote and escape
-      // for the execute(format()) call. Maybe we should change the `default_value` to only accept a string instead ?
-      default_value_format === 'expression' ? default_value : `'${literal(String(default_value))}'`
-    defaultValueClause = `DEFAULT ${formattedDefault}`
+      default_value_format === 'expression' ? default_value : literal(default_value)
path: packages/pg-meta/src/pg-meta-columns.ts
user: login: sweatybridge
body: see https://github.com/supabase/postgres-meta/blob/master/src/lib/PostgresMetaColumns.ts#L165-L168
created_at: 2025-05-07T16:17:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,17 +25,30 @@ export async function createDatabaseColumn({
   let headers = new Headers()
   if (connectionString) headers.set('x-connection-encrypted', connectionString)
 
-  const { data, error } = await post('/platform/pg-meta/{ref}/columns', {
-    params: {
-      header: { 'x-connection-encrypted': connectionString! },
-      path: { ref: projectRef },
-    },
-    body: payload,
-    headers,
+  const { sql } = pgMeta.columns.create({
+    schema: payload.schema,
+    table: payload.table,
+    name: payload.name,
+    type: payload.type,
+    default_value: payload.defaultValue,
+    default_value_format: payload.defaultValueFormat,
+    is_identity: payload.isIdentity,
+    identity_generation: payload.identityGeneration,
+    is_nullable: payload.isNullable,
+    is_primary_key: payload.isPrimaryKey,
+    is_unique: payload.isUnique,
+    comment: payload.comment,
+    check: payload.check,
+  })
+
+  const { result } = await executeSql({
+    projectRef,
+    connectionString,
+    sql,
+    queryKey: ['column', 'create'],
   })
path: apps/studio/data/database-columns/database-column-create-mutation.ts
user: login: avallete
body: **issue**

This will throw an error when `connectionString` is not valid (which happen often with new project until an ipv6 is setup), we handled the error with `handleError` before, we wanna do the same I think:

```ts
try {
    await executeSql(...)
    return data
} catch (error) {
   handleError(error)
}
```
created_at: 2025-05-12T07:41:49Z
author_association: MEMBER
diff_hunk: @@ -21,17 +25,30 @@ export async function createDatabaseColumn({
   let headers = new Headers()
   if (connectionString) headers.set('x-connection-encrypted', connectionString)
 
-  const { data, error } = await post('/platform/pg-meta/{ref}/columns', {
-    params: {
-      header: { 'x-connection-encrypted': connectionString! },
-      path: { ref: projectRef },
-    },
-    body: payload,
-    headers,
+  const { sql } = pgMeta.columns.create({
+    schema: payload.schema,
+    table: payload.table,
+    name: payload.name,
+    type: payload.type,
+    default_value: payload.defaultValue,
+    default_value_format: payload.defaultValueFormat,
+    is_identity: payload.isIdentity,
+    identity_generation: payload.identityGeneration,
+    is_nullable: payload.isNullable,
+    is_primary_key: payload.isPrimaryKey,
+    is_unique: payload.isUnique,
+    comment: payload.comment,
+    check: payload.check,
+  })
+
+  const { result } = await executeSql({
+    projectRef,
+    connectionString,
+    sql,
+    queryKey: ['column', 'create'],
   })
path: apps/studio/data/database-columns/database-column-create-mutation.ts
user: login: sweatybridge
body: The [handleError](https://github.com/supabase/supabase/blob/master/apps/studio/data/sql/execute-sql-query.ts#L102-L103) is called within executeSql so not necessary here.
created_at: 2025-05-12T15:47:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,17 +25,30 @@ export async function createDatabaseColumn({
   let headers = new Headers()
   if (connectionString) headers.set('x-connection-encrypted', connectionString)
 
-  const { data, error } = await post('/platform/pg-meta/{ref}/columns', {
-    params: {
-      header: { 'x-connection-encrypted': connectionString! },
-      path: { ref: projectRef },
-    },
-    body: payload,
-    headers,
+  const { sql } = pgMeta.columns.create({
+    schema: payload.schema,
+    table: payload.table,
+    name: payload.name,
+    type: payload.type,
+    default_value: payload.defaultValue,
+    default_value_format: payload.defaultValueFormat,
+    is_identity: payload.isIdentity,
+    identity_generation: payload.identityGeneration,
+    is_nullable: payload.isNullable,
+    is_primary_key: payload.isPrimaryKey,
+    is_unique: payload.isUnique,
+    comment: payload.comment,
+    check: payload.check,
+  })
+
+  const { result } = await executeSql({
+    projectRef,
+    connectionString,
+    sql,
+    queryKey: ['column', 'create'],
   })
path: apps/studio/data/database-columns/database-column-create-mutation.ts
user: login: avallete
body: Not exactly at the same level though with the old code, we were always calling the `post` method, then the `pgMetaGuard` would take place and would return an error if the connectionString wasn't valid, which would result in always calling `handleError` because `if(error)` would be true.

The logic within `executeSql` throw before the call to `post`, so the `handleError` won't be called in this case. 
Might wanna either: 

1. Remove the early check here: https://github.com/supabase/supabase/blob/71b261932e394de25d95a939ccbb228c622968a9/apps/studio/data/sql/execute-sql-query.ts#L44-L45 as the guard will already be in `post`
2. Keep the guard, and use a try/catch to call `handleError`.
created_at: 2025-05-13T07:40:32Z
author_association: MEMBER
diff_hunk: @@ -21,17 +25,30 @@ export async function createDatabaseColumn({
   let headers = new Headers()
   if (connectionString) headers.set('x-connection-encrypted', connectionString)
 
-  const { data, error } = await post('/platform/pg-meta/{ref}/columns', {
-    params: {
-      header: { 'x-connection-encrypted': connectionString! },
-      path: { ref: projectRef },
-    },
-    body: payload,
-    headers,
+  const { sql } = pgMeta.columns.create({
+    schema: payload.schema,
+    table: payload.table,
+    name: payload.name,
+    type: payload.type,
+    default_value: payload.defaultValue,
+    default_value_format: payload.defaultValueFormat,
+    is_identity: payload.isIdentity,
+    identity_generation: payload.identityGeneration,
+    is_nullable: payload.isNullable,
+    is_primary_key: payload.isPrimaryKey,
+    is_unique: payload.isUnique,
+    comment: payload.comment,
+    check: payload.check,
+  })
+
+  const { result } = await executeSql({
+    projectRef,
+    connectionString,
+    sql,
+    queryKey: ['column', 'create'],
   })
path: apps/studio/data/database-columns/database-column-create-mutation.ts
user: login: avallete
body: Made a PR applying 1 here: https://github.com/supabase/supabase/pull/35635
created_at: 2025-05-13T07:47:11Z
author_association: MEMBER

PR comments:
user: login: vercel[bot]
created_at: 2025-05-07T16:08:36Z
body: [vc]: #pdsOAVu7IzbnmOJV1hg+7eoLWRCC8gJRfeFATi0BYPE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ6b25lLXd3dy1kb3QtY29tIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvd3d3IiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2Uvem9uZS13d3ctZG90LWNvbS82RXdjTmQxNnFoWTZna1Zpa3lDRE0zRjY0OFR3IiwicHJldmlld1VybCI6IiIsIm5leHRDb21taXRTdGF0dXMiOiJTS0lQUEVEIn0seyJuYW1lIjoiZG9jcyIsInJvb3REaXJlY3RvcnkiOiJhcHBzL2RvY3MiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9zdXBhYmFzZS9kb2NzLzhGZG5VVGRmWnhUVTRqTE5ZY2t5MWsyUmhCTnAiLCJwcmV2aWV3VXJsIjoiIiwibmV4dENvbW1pdFN0YXR1cyI6IlNLSVBQRUQifSx7Im5hbWUiOiJzdHVkaW8tc2VsZi1ob3N0ZWQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9zdHVkaW8iLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2Uvc3R1ZGlvLXNlbGYtaG9zdGVkL0Q0anVIZlpaUlkxYldpWnY4b1h3M25hcTlxZTYiLCJwcmV2aWV3VXJsIjoic3R1ZGlvLXNlbGYtaG9zdGVkLWdpdC1xaWFvLWNvbHVtbi1jcmVhdGUtc3VwYWJhc2UudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzdHVkaW8tc2VsZi1ob3N0ZWQtZ2l0LXFpYW8tY29sdW1uLWNyZWF0ZS1zdXBhYmFzZS52ZXJjZWwuYXBwIn19LHsibmFtZSI6InN0dWRpbyIsInJvb3REaXJlY3RvcnkiOiJhcHBzL3N0dWRpbyIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9zdXBhYmFzZS9zdHVkaW8vRXdFTU5wM1V5c1hHQmIybXpSYWJ5SDZad2JBSiIsInByZXZpZXdVcmwiOiJzdHVkaW8tZ2l0LXFpYW8tY29sdW1uLWNyZWF0ZS1zdXBhYmFzZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IklHTk9SRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn19LHsibmFtZSI6InN0dWRpby1zdGFnaW5nIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvc3R1ZGlvIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3N1cGFiYXNlL3N0dWRpby1zdGFnaW5nLzlLUm1rUXJMN3FxWWtjMkxGUmNMM0NQWFMzakIiLCJwcmV2aWV3VXJsIjoic3R1ZGlvLXN0YWdpbmctZ2l0LXFpYW8tY29sdW1uLWNyZWF0ZS1zdXBhYmFzZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InN0dWRpby1zdGFnaW5nLWdpdC1xaWFvLWNvbHVtbi1jcmVhdGUtc3VwYWJhc2UudmVyY2VsLmFwcCJ9fSx7Im5hbWUiOiJkZXNpZ24tc3lzdGVtIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvZGVzaWduLXN5c3RlbSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3N1cGFiYXNlL2Rlc2lnbi1zeXN0ZW0vN0F0VTcxZzUzQkhEWG01bXFWbXdXOXpHWHVGRSIsInByZXZpZXdVcmwiOiIiLCJuZXh0Q29tbWl0U3RhdHVzIjoiU0tJUFBFRCJ9LHsibmFtZSI6InVpLWxpYnJhcnkiLCJyb290RGlyZWN0b3J5IjoiYXBwcy91aS1saWJyYXJ5IiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2UvdWktbGlicmFyeS82VnNjMjM2V2FCMkpXU3NnanBLWUpLZENjUGdIIiwicHJldmlld1VybCI6IiIsIm5leHRDb21taXRTdGF0dXMiOiJTS0lQUEVEIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **studio-self-hosted** | ✅ Ready ([Inspect](https://vercel.com/supabase/studio-self-hosted/D4juHfZZRY1bWiZv8oXw3naq9qe6)) | [Visit Preview](https://studio-self-hosted-git-qiao-column-create-supabase.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/studio-self-hosted-git-qiao-column-create-supabase.vercel.app?via=pr-comment-feedback-link) | May 14, 2025 5:09am |
| **studio-staging** | ✅ Ready ([Inspect](https://vercel.com/supabase/studio-staging/9KRmkQrL7qqYkc2LFRcL3CPXS3jB)) | [Visit Preview](https://studio-staging-git-qiao-column-create-supabase.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/studio-staging-git-qiao-column-create-supabase.vercel.app?via=pr-comment-feedback-link) | May 14, 2025 5:09am |

<details><summary>5 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **studio** | ⬜️ Ignored ([Inspect](https://vercel.com/supabase/studio/EwEMNp3UysXGBb2mzRabyH6ZwbAJ)) | [Visit Preview](https://studio-git-qiao-column-create-supabase.vercel.app) |  | May 14, 2025 5:09am |
| **design-system** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/design-system/7AtU71g53BHDXm5mqVmwW9zGXuFE)) |  |  | May 14, 2025 5:09am |
| **docs** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/docs/8FdnUTdfZxTU4jLNYcky1k2RhBNp)) |  |  | May 14, 2025 5:09am |
| **ui-library** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/ui-library/6Vsc236WaB2JWSsgjpKYJKdCcPgH)) |  |  | May 14, 2025 5:09am |
| **zone-www-dot-com** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/zone-www-dot-com/6EwcNd16qhY6gkVikyCDM3F648Tw)) |  |  | May 14, 2025 5:09am |
</details>








author_association: NONE
user: login: supabase[bot]
created_at: 2025-05-07T16:08:37Z
body: [supa]:xguihxuzqibwxjnimxev
This pull request has been ignored for the connected project `xguihxuzqibwxjnimxev` because there are no changes detected in `supabase` directory. You can change this behaviour in [Project Integrations Settings ↗︎](https://supabase.com/dashboard/project/xguihxuzqibwxjnimxev/settings/integrations).

<hr/>

Preview Branches by Supabase.
Learn more about [Supabase Branching ↗︎](https://supabase.com/docs/guides/platform/branching).
author_association: NONE
user: login: coveralls
created_at: 2025-05-09T06:59:02Z
body: 
[![Coverage Status](https://coveralls.io/builds/73658482/badge)](https://coveralls.io/builds/73658482)

coverage: 61.831%. remained the same
when pulling **33a96998d364ac08dffb78e0df6889f62cafee06 on qiao/column-create**
into **71b261932e394de25d95a939ccbb228c622968a9 on master**.

author_association: NONE
user: login: codecov-commenter
created_at: 2025-05-09T09:22:27Z
body: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/supabase/supabase/pull/35532?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase) Report
Attention: Patch coverage is `94.11765%` with `2 lines` in your changes missing coverage. Please review.
> Please [upload](https://docs.codecov.com/docs/codecov-uploader) report for BASE (`master@73f38a4`). [Learn more](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase#section-missing-base-commit) about missing BASE report.

| [Files with missing lines](https://app.codecov.io/gh/supabase/supabase/pull/35532?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase) | Patch % | Lines |
|---|---|---|
| [packages/pg-meta/src/pg-meta-columns.ts](https://app.codecov.io/gh/supabase/supabase/pull/35532?src=pr&el=tree&filepath=packages%2Fpg-meta%2Fsrc%2Fpg-meta-columns.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase#diff-cGFja2FnZXMvcGctbWV0YS9zcmMvcGctbWV0YS1jb2x1bW5zLnRz) | 94.11% | [2 Missing :warning: ](https://app.codecov.io/gh/supabase/supabase/pull/35532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase) |

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##             master   #35532   +/-   ##
=========================================
  Coverage          ?   92.20%           
=========================================
  Files             ?       49           
  Lines             ?     2924           
  Branches          ?      519           
=========================================
  Hits              ?     2696           
  Misses            ?      228           
  Partials          ?        0           
```

| [Flag](https://app.codecov.io/gh/supabase/supabase/pull/35532/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase) | Coverage Δ | |
|---|---|---|
| [pg-meta](https://app.codecov.io/gh/supabase/supabase/pull/35532/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase) | `92.20% <94.11%> (?)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/supabase/supabase/pull/35532?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=supabase).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE