import PostgrestFilterBuilder from './PostgrestFilterBuilder' import { GetResult } from './select-query-parser/result' import { ClientServerOptions, Fetch, GenericSchema, GenericTable, GenericView, } from './types/common/common' export default class PostgrestQueryBuilder< ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Relation extends GenericTable | GenericView, RelationName = unknown, Relationships = Relation extends { Relationships: infer R } ? R : unknown, > { url: URL headers: Headers schema?: string signal?: AbortSignal fetch?: Fetch urlLengthLimit: number /** * Creates a query builder scoped to a Postgres table or view. * * @example * ```ts * import PostgrestQueryBuilder from '@supabase/postgrest-js' * * const query = new PostgrestQueryBuilder( * new URL('https://xyzcompany.supabase.co/rest/v1/users'), * { headers: { apikey: 'public-anon-key' } } * ) * ``` */ constructor( url: URL, { headers = {}, schema, fetch, urlLengthLimit = 8000, }: { headers?: HeadersInit schema?: string fetch?: Fetch urlLengthLimit?: number } ) { this.url = url this.headers = new Headers(headers) this.schema = schema this.fetch = fetch this.urlLengthLimit = urlLengthLimit } /** * Clone URL and headers to prevent shared state between operations. */ private cloneRequestState(): { url: URL; headers: Headers } { return { url: new URL(this.url.toString()), headers: new Headers(this.headers), } } /** * Perform a SELECT query on the table or view. * * @param columns - The columns to retrieve, separated by commas. Columns can be renamed when returned with `customName:columnName` * * @param options - Named parameters * * @param options.head - When set to `true`, `data` will not be returned. * Useful if you only need the count. * * @param options.count - Count algorithm to use to count rows in the table or view. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @remarks * When using `count` with `.range()` or `.limit()`, the returned `count` is the total number of rows * that match your filters, not the number of rows in the current page. Use this to build pagination UI. * - By default, Supabase projects return a maximum of 1,000 rows. This setting can be changed in your project's [API settings](/dashboard/project/_/settings/api). It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data. * - `select()` can be combined with [Filters](/docs/reference/javascript/using-filters) * - `select()` can be combined with [Modifiers](/docs/reference/javascript/using-modifiers) * - `apikey` is a reserved keyword if you're using the [Supabase Platform](/docs/guides/platform) and [should be avoided as a column name](https://github.com/supabase/supabase/issues/5465). * * @category Database * * @example Getting your data * ```js * const { data, error } = await supabase * .from('characters') * .select() * ``` * * @exampleSql Getting your data * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Harry'), * (2, 'Frodo'), * (3, 'Katniss'); * ``` * * @exampleResponse Getting your data * ```json * { * "data": [ * { * "id": 1, * "name": "Harry" * }, * { * "id": 2, * "name": "Frodo" * }, * { * "id": 3, * "name": "Katniss" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example Selecting specific columns * ```js * const { data, error } = await supabase * .from('characters') * .select('name') * ``` * * @exampleSql Selecting specific columns * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Frodo'), * (2, 'Harry'), * (3, 'Katniss'); * ``` * * @exampleResponse Selecting specific columns * ```json * { * "data": [ * { * "name": "Frodo" * }, * { * "name": "Harry" * }, * { * "name": "Katniss" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query referenced tables * If your database has foreign key relationships, you can query related tables too. * * @example Query referenced tables * ```js * const { data, error } = await supabase * .from('orchestral_sections') * .select(` * name, * instruments ( * name * ) * `) * ``` * * @exampleSql Query referenced tables * ```sql * create table * orchestral_sections (id int8 primary key, name text); * create table * instruments ( * id int8 primary key, * section_id int8 not null references orchestral_sections, * name text * ); * * insert into * orchestral_sections (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * instruments (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse Query referenced tables * ```json * { * "data": [ * { * "name": "strings", * "instruments": [ * { * "name": "violin" * } * ] * }, * { * "name": "woodwinds", * "instruments": [ * { * "name": "flute" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query referenced tables with spaces in their names * If your table name contains spaces, you must use double quotes in the `select` statement to reference the table. * * @example Query referenced tables with spaces in their names * ```js * const { data, error } = await supabase * .from('orchestral sections') * .select(` * name, * "musical instruments" ( * name * ) * `) * ``` * * @exampleSql Query referenced tables with spaces in their names * ```sql * create table * "orchestral sections" (id int8 primary key, name text); * create table * "musical instruments" ( * id int8 primary key, * section_id int8 not null references "orchestral sections", * name text * ); * * insert into * "orchestral sections" (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * "musical instruments" (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse Query referenced tables with spaces in their names * ```json * { * "data": [ * { * "name": "strings", * "musical instruments": [ * { * "name": "violin" * } * ] * }, * { * "name": "woodwinds", * "musical instruments": [ * { * "name": "flute" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query referenced tables through a join table * If you're in a situation where your tables are **NOT** directly * related, but instead are joined by a _join table_, you can still use * the `select()` method to query the related data. The join table needs * to have the foreign keys as part of its composite primary key. * * @example Query referenced tables through a join table * ```ts * const { data, error } = await supabase * .from('users') * .select(` * name, * teams ( * name * ) * `) * * ``` * * @exampleSql Query referenced tables through a join table * ```sql * create table * users ( * id int8 primary key, * name text * ); * create table * teams ( * id int8 primary key, * name text * ); * -- join table * create table * users_teams ( * user_id int8 not null references users, * team_id int8 not null references teams, * -- both foreign keys must be part of a composite primary key * primary key (user_id, team_id) * ); * * insert into * users (id, name) * values * (1, 'Kiran'), * (2, 'Evan'); * insert into * teams (id, name) * values * (1, 'Green'), * (2, 'Blue'); * insert into * users_teams (user_id, team_id) * values * (1, 1), * (1, 2), * (2, 2); * ``` * * @exampleResponse Query referenced tables through a join table * ```json * { * "data": [ * { * "name": "Kiran", * "teams": [ * { * "name": "Green" * }, * { * "name": "Blue" * } * ] * }, * { * "name": "Evan", * "teams": [ * { * "name": "Blue" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * * ``` * * @exampleDescription Query the same referenced table multiple times * If you need to query the same referenced table twice, use the name of the * joined column to identify which join to use. You can also give each * column an alias. * * @example Query the same referenced table multiple times * ```ts * const { data, error } = await supabase * .from('messages') * .select(` * content, * from:sender_id(name), * to:receiver_id(name) * `) * * // To infer types, use the name of the table (in this case `users`) and * // the name of the foreign key constraint. * const { data, error } = await supabase * .from('messages') * .select(` * content, * from:users!messages_sender_id_fkey(name), * to:users!messages_receiver_id_fkey(name) * `) * ``` * * @exampleSql Query the same referenced table multiple times * ```sql * create table * users (id int8 primary key, name text); * * create table * messages ( * sender_id int8 not null references users, * receiver_id int8 not null references users, * content text * ); * * insert into * users (id, name) * values * (1, 'Kiran'), * (2, 'Evan'); * * insert into * messages (sender_id, receiver_id, content) * values * (1, 2, '👋'); * ``` * ``` * * @exampleResponse Query the same referenced table multiple times * ```json * { * "data": [ * { * "content": "👋", * "from": { * "name": "Kiran" * }, * "to": { * "name": "Evan" * } * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query nested foreign tables through a join table * You can use the result of a joined table to gather data in * another foreign table. With multiple references to the same foreign * table you must specify the column on which to conduct the join. * * @example Query nested foreign tables through a join table * ```ts * const { data, error } = await supabase * .from('games') * .select(` * game_id:id, * away_team:teams!games_away_team_fkey ( * users ( * id, * name * ) * ) * `) * * ``` * * @exampleSql Query nested foreign tables through a join table * ```sql * ```sql * create table * users ( * id int8 primary key, * name text * ); * create table * teams ( * id int8 primary key, * name text * ); * -- join table * create table * users_teams ( * user_id int8 not null references users, * team_id int8 not null references teams, * * primary key (user_id, team_id) * ); * create table * games ( * id int8 primary key, * home_team int8 not null references teams, * away_team int8 not null references teams, * name text * ); * * insert into users (id, name) * values * (1, 'Kiran'), * (2, 'Evan'); * insert into * teams (id, name) * values * (1, 'Green'), * (2, 'Blue'); * insert into * users_teams (user_id, team_id) * values * (1, 1), * (1, 2), * (2, 2); * insert into * games (id, home_team, away_team, name) * values * (1, 1, 2, 'Green vs Blue'), * (2, 2, 1, 'Blue vs Green'); * ``` * * @exampleResponse Query nested foreign tables through a join table * ```json * { * "data": [ * { * "game_id": 1, * "away_team": { * "users": [ * { * "id": 1, * "name": "Kiran" * }, * { * "id": 2, * "name": "Evan" * } * ] * } * }, * { * "game_id": 2, * "away_team": { * "users": [ * { * "id": 1, * "name": "Kiran" * } * ] * } * } * ], * "status": 200, * "statusText": "OK" * } * * ``` * * @exampleDescription Filtering through referenced tables * If the filter on a referenced table's column is not satisfied, the referenced * table returns `[]` or `null` but the parent table is not filtered out. * If you want to filter out the parent table rows, use the `!inner` hint * * @example Filtering through referenced tables * ```ts * const { data, error } = await supabase * .from('instruments') * .select('name, orchestral_sections(*)') * .eq('orchestral_sections.name', 'percussion') * ``` * * @exampleSql Filtering through referenced tables * ```sql * create table * orchestral_sections (id int8 primary key, name text); * create table * instruments ( * id int8 primary key, * section_id int8 not null references orchestral_sections, * name text * ); * * insert into * orchestral_sections (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * instruments (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse Filtering through referenced tables * ```json * { * "data": [ * { * "name": "flute", * "orchestral_sections": null * }, * { * "name": "violin", * "orchestral_sections": null * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Querying referenced table with count * You can get the number of rows in a related table by using the * **count** property. * * @example Querying referenced table with count * ```ts * const { data, error } = await supabase * .from('orchestral_sections') * .select(`*, instruments(count)`) * ``` * * @exampleSql Querying referenced table with count * ```sql * create table orchestral_sections ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text * ); * * create table characters ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text, * "section_id" "uuid" references public.orchestral_sections on delete cascade * ); * * with section as ( * insert into orchestral_sections (name) * values ('strings') returning id * ) * insert into instruments (name, section_id) values * ('violin', (select id from section)), * ('viola', (select id from section)), * ('cello', (select id from section)), * ('double bass', (select id from section)); * ``` * * @exampleResponse Querying referenced table with count * ```json * [ * { * "id": "693694e7-d993-4360-a6d7-6294e325d9b6", * "name": "strings", * "instruments": [ * { * "count": 4 * } * ] * } * ] * ``` * * @exampleDescription Querying with count option * You can get the number of rows by using the * [count](/docs/reference/javascript/select#parameters) option. * * @example Querying with count option * ```ts * const { count, error } = await supabase * .from('characters') * .select('*', { count: 'exact', head: true }) * ``` * * @exampleSql Querying with count option * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse Querying with count option * ```json * { * "count": 3, * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Querying JSON data * You can select and filter data inside of * [JSON](/docs/guides/database/json) columns. Postgres offers some * [operators](/docs/guides/database/json#query-the-jsonb-data) for * querying JSON data. * * @example Querying JSON data * ```ts * const { data, error } = await supabase * .from('users') * .select(` * id, name, * address->city * `) * ``` * * @exampleSql Querying JSON data * ```sql * create table * users ( * id int8 primary key, * name text, * address jsonb * ); * * insert into * users (id, name, address) * values * (1, 'Frodo', '{"city":"Hobbiton"}'); * ``` * * @exampleResponse Querying JSON data * ```json * { * "data": [ * { * "id": 1, * "name": "Frodo", * "city": "Hobbiton" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Querying referenced table with inner join * If you don't want to return the referenced table contents, you can leave the parenthesis empty. * Like `.select('name, orchestral_sections!inner()')`. * * @example Querying referenced table with inner join * ```ts * const { data, error } = await supabase * .from('instruments') * .select('name, orchestral_sections!inner(name)') * .eq('orchestral_sections.name', 'woodwinds') * .limit(1) * ``` * * @exampleSql Querying referenced table with inner join * ```sql * create table orchestral_sections ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text * ); * * create table instruments ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text, * "section_id" "uuid" references public.orchestral_sections on delete cascade * ); * * with section as ( * insert into orchestral_sections (name) * values ('woodwinds') returning id * ) * insert into instruments (name, section_id) values * ('flute', (select id from section)), * ('clarinet', (select id from section)), * ('bassoon', (select id from section)), * ('piccolo', (select id from section)); * ``` * * @exampleResponse Querying referenced table with inner join * ```json * { * "data": [ * { * "name": "flute", * "orchestral_sections": {"name": "woodwinds"} * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Switching schemas per query * In addition to setting the schema during initialization, you can also switch schemas on a per-query basis. * Make sure you've set up your [database privileges and API settings](/docs/guides/api/using-custom-schemas). * * @example Switching schemas per query * ```ts * const { data, error } = await supabase * .schema('myschema') * .from('mytable') * .select() * ``` * * @exampleSql Switching schemas per query * ```sql * create schema myschema; * * create table myschema.mytable ( * id uuid primary key default gen_random_uuid(), * data text * ); * * insert into myschema.mytable (data) values ('mydata'); * ``` * * @exampleResponse Switching schemas per query * ```json * { * "data": [ * { * "id": "4162e008-27b0-4c0f-82dc-ccaeee9a624d", * "data": "mydata" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ select< Query extends string = '*', ResultOne = GetResult< Schema, Relation['Row'], RelationName, Relationships, Query, ClientOptions >, >( columns?: Query, options?: { head?: boolean count?: 'exact' | 'planned' | 'estimated' } ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], ResultOne[], RelationName, Relationships, 'GET' > { const { head = false, count } = options ?? {} const method = head ? 'HEAD' : 'GET' // Remove whitespaces except when quoted let quoted = false const cleanedColumns = (columns ?? '*') .split('') .map((c) => { if (/\s/.test(c) && !quoted) { return '' } if (c === '"') { quoted = !quoted } return c }) .join('') const { url, headers } = this.cloneRequestState() url.searchParams.set('select', cleanedColumns) if (count) { headers.append('Prefer', `count=${count}`) } return new PostgrestFilterBuilder({ method, url, headers, schema: this.schema, fetch: this.fetch, urlLengthLimit: this.urlLengthLimit, }) } // TODO(v3): Make `defaultToNull` consistent for both single & bulk inserts. insert( values: Row, options?: { count?: 'exact' | 'planned' | 'estimated' } ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'POST' > insert( values: Row[], options?: { count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'POST' > /** * Perform an INSERT into the table or view. * * By default, inserted rows are not returned. To return it, chain the call * with `.select()`. * * @param values - The values to insert. Pass an object to insert a single row * or an array to insert multiple rows. * * @param options - Named parameters * * @param options.count - Count algorithm to use to count inserted rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @param options.defaultToNull - Make missing fields default to `null`. * Otherwise, use the default value for the column. Only applies for bulk * inserts. * * @category Database * * @example Create a record * ```ts * const { error } = await supabase * .from('countries') * .insert({ id: 1, name: 'Mordor' }) * ``` * * @exampleSql Create a record * ```sql * create table * countries (id int8 primary key, name text); * ``` * * @exampleResponse Create a record * ```json * { * "status": 201, * "statusText": "Created" * } * ``` * * @example Create a record and return it * ```ts * const { data, error } = await supabase * .from('countries') * .insert({ id: 1, name: 'Mordor' }) * .select() * ``` * * @exampleSql Create a record and return it * ```sql * create table * countries (id int8 primary key, name text); * ``` * * @exampleResponse Create a record and return it * ```json * { * "data": [ * { * "id": 1, * "name": "Mordor" * } * ], * "status": 201, * "statusText": "Created" * } * ``` * * @exampleDescription Bulk create * A bulk create operation is handled in a single transaction. * If any of the inserts fail, none of the rows are inserted. * * @example Bulk create * ```ts * const { error } = await supabase * .from('countries') * .insert([ * { id: 1, name: 'Mordor' }, * { id: 1, name: 'The Shire' }, * ]) * ``` * * @exampleSql Bulk create * ```sql * create table * countries (id int8 primary key, name text); * ``` * * @exampleResponse Bulk create * ```json * { * "error": { * "code": "23505", * "details": "Key (id)=(1) already exists.", * "hint": null, * "message": "duplicate key value violates unique constraint \"countries_pkey\"" * }, * "status": 409, * "statusText": "Conflict" * } * ``` */ insert( values: Row | Row[], { count, defaultToNull = true, }: { count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } = {} ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'POST' > { const method = 'POST' const { url, headers } = this.cloneRequestState() if (count) { headers.append('Prefer', `count=${count}`) } if (!defaultToNull) { headers.append('Prefer', `missing=default`) } if (Array.isArray(values)) { const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), [] as string[]) if (columns.length > 0) { const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`) url.searchParams.set('columns', uniqueColumns.join(',')) } } return new PostgrestFilterBuilder({ method, url, headers, schema: this.schema, body: values, fetch: this.fetch ?? fetch, urlLengthLimit: this.urlLengthLimit, }) } // TODO(v3): Make `defaultToNull` consistent for both single & bulk upserts. upsert( values: Row, options?: { onConflict?: string ignoreDuplicates?: boolean count?: 'exact' | 'planned' | 'estimated' } ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'POST' > upsert( values: Row[], options?: { onConflict?: string ignoreDuplicates?: boolean count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'POST' > /** * Perform an UPSERT on the table or view. Depending on the column(s) passed * to `onConflict`, `.upsert()` allows you to perform the equivalent of * `.insert()` if a row with the corresponding `onConflict` columns doesn't * exist, or if it does exist, perform an alternative action depending on * `ignoreDuplicates`. * * By default, upserted rows are not returned. To return it, chain the call * with `.select()`. * * @param values - The values to upsert with. Pass an object to upsert a * single row or an array to upsert multiple rows. * * @param options - Named parameters * * @param options.onConflict - Comma-separated UNIQUE column(s) to specify how * duplicate rows are determined. Two rows are duplicates if all the * `onConflict` columns are equal. * * @param options.ignoreDuplicates - If `true`, duplicate rows are ignored. If * `false`, duplicate rows are merged with existing rows. * * @param options.count - Count algorithm to use to count upserted rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @param options.defaultToNull - Make missing fields default to `null`. * Otherwise, use the default value for the column. This only applies when * inserting new rows, not when merging with existing rows under * `ignoreDuplicates: false`. This also only applies when doing bulk upserts. * * @example Upsert a single row using a unique key * ```ts * // Upserting a single row, overwriting based on the 'username' unique column * const { data, error } = await supabase * .from('users') * .upsert({ username: 'supabot' }, { onConflict: 'username' }) * * // Example response: * // { * // data: [ * // { id: 4, message: 'bar', username: 'supabot' } * // ], * // error: null * // } * ``` * * @example Upsert with conflict resolution and exact row counting * ```ts * // Upserting and returning exact count * const { data, error, count } = await supabase * .from('users') * .upsert( * { * id: 3, * message: 'foo', * username: 'supabot' * }, * { * onConflict: 'username', * count: 'exact' * } * ) * * // Example response: * // { * // data: [ * // { * // id: 42, * // handle: "saoirse", * // display_name: "Saoirse" * // } * // ], * // count: 1, * // error: null * // } * ``` * * @category Database * * @remarks * - Primary keys must be included in `values` to use upsert. * * @example Upsert your data * ```ts * const { data, error } = await supabase * .from('instruments') * .upsert({ id: 1, name: 'piano' }) * .select() * ``` * * @exampleSql Upsert your data * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Upsert your data * ```json * { * "data": [ * { * "id": 1, * "name": "piano" * } * ], * "status": 201, * "statusText": "Created" * } * ``` * * @example Bulk Upsert your data * ```ts * const { data, error } = await supabase * .from('instruments') * .upsert([ * { id: 1, name: 'piano' }, * { id: 2, name: 'harp' }, * ]) * .select() * ``` * * @exampleSql Bulk Upsert your data * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Bulk Upsert your data * ```json * { * "data": [ * { * "id": 1, * "name": "piano" * }, * { * "id": 2, * "name": "harp" * } * ], * "status": 201, * "statusText": "Created" * } * ``` * * @exampleDescription Upserting into tables with constraints * In the following query, `upsert()` implicitly uses the `id` * (primary key) column to determine conflicts. If there is no existing * row with the same `id`, `upsert()` inserts a new row, which * will fail in this case as there is already a row with `handle` `"saoirse"`. * Using the `onConflict` option, you can instruct `upsert()` to use * another column with a unique constraint to determine conflicts. * * @example Upserting into tables with constraints * ```ts * const { data, error } = await supabase * .from('users') * .upsert({ id: 42, handle: 'saoirse', display_name: 'Saoirse' }) * .select() * ``` * * @exampleSql Upserting into tables with constraints * ```sql * create table * users ( * id int8 generated by default as identity primary key, * handle text not null unique, * display_name text * ); * * insert into * users (id, handle, display_name) * values * (1, 'saoirse', null); * ``` * * @exampleResponse Upserting into tables with constraints * ```json * { * "error": { * "code": "23505", * "details": "Key (handle)=(saoirse) already exists.", * "hint": null, * "message": "duplicate key value violates unique constraint \"users_handle_key\"" * }, * "status": 409, * "statusText": "Conflict" * } * ``` */ upsert( values: Row | Row[], { onConflict, ignoreDuplicates = false, count, defaultToNull = true, }: { onConflict?: string ignoreDuplicates?: boolean count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } = {} ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'POST' > { const method = 'POST' const { url, headers } = this.cloneRequestState() headers.append('Prefer', `resolution=${ignoreDuplicates ? 'ignore' : 'merge'}-duplicates`) if (onConflict !== undefined) url.searchParams.set('on_conflict', onConflict) if (count) { headers.append('Prefer', `count=${count}`) } if (!defaultToNull) { headers.append('Prefer', 'missing=default') } if (Array.isArray(values)) { const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), [] as string[]) if (columns.length > 0) { const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`) url.searchParams.set('columns', uniqueColumns.join(',')) } } return new PostgrestFilterBuilder({ method, url, headers, schema: this.schema, body: values, fetch: this.fetch ?? fetch, urlLengthLimit: this.urlLengthLimit, }) } /** * Perform an UPDATE on the table or view. * * By default, updated rows are not returned. To return it, chain the call * with `.select()` after filters. * * @param values - The values to update with * * @param options - Named parameters * * @param options.count - Count algorithm to use to count updated rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @category Database * * @remarks * - `update()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to update. * * @example Updating your data * ```ts * const { error } = await supabase * .from('instruments') * .update({ name: 'piano' }) * .eq('id', 1) * ``` * * @exampleSql Updating your data * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Updating your data * ```json * { * "status": 204, * "statusText": "No Content" * } * ``` * * @example Update a record and return it * ```ts * const { data, error } = await supabase * .from('instruments') * .update({ name: 'piano' }) * .eq('id', 1) * .select() * ``` * * @exampleSql Update a record and return it * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Update a record and return it * ```json * { * "data": [ * { * "id": 1, * "name": "piano" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Updating JSON data * Postgres offers some * [operators](/docs/guides/database/json#query-the-jsonb-data) for * working with JSON data. Currently, it is only possible to update the entire JSON document. * * @example Updating JSON data * ```ts * const { data, error } = await supabase * .from('users') * .update({ * address: { * street: 'Melrose Place', * postcode: 90210 * } * }) * .eq('address->postcode', 90210) * .select() * ``` * * @exampleSql Updating JSON data * ```sql * create table * users ( * id int8 primary key, * name text, * address jsonb * ); * * insert into * users (id, name, address) * values * (1, 'Michael', '{ "postcode": 90210 }'); * ``` * * @exampleResponse Updating JSON data * ```json * { * "data": [ * { * "id": 1, * "name": "Michael", * "address": { * "street": "Melrose Place", * "postcode": 90210 * } * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ update( values: Row, { count, }: { count?: 'exact' | 'planned' | 'estimated' } = {} ): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'PATCH' > { const method = 'PATCH' const { url, headers } = this.cloneRequestState() if (count) { headers.append('Prefer', `count=${count}`) } return new PostgrestFilterBuilder({ method, url, headers, schema: this.schema, body: values, fetch: this.fetch ?? fetch, urlLengthLimit: this.urlLengthLimit, }) } /** * Perform a DELETE on the table or view. * * By default, deleted rows are not returned. To return it, chain the call * with `.select()` after filters. * * @param options - Named parameters * * @param options.count - Count algorithm to use to count deleted rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. */ delete({ count, }: { count?: 'exact' | 'planned' | 'estimated' } = {}): PostgrestFilterBuilder< ClientOptions, Schema, Relation['Row'], null, RelationName, Relationships, 'DELETE' > { const method = 'DELETE' const { url, headers } = this.cloneRequestState() if (count) { headers.append('Prefer', `count=${count}`) } return new PostgrestFilterBuilder({ method, url, headers, schema: this.schema, fetch: this.fetch ?? fetch, urlLengthLimit: this.urlLengthLimit, }) } }