Skip to content

Commit 5830e80

Browse files
authored
Merge pull request #72 from supabase/feat/replica-identity
Add replica identity fields to /tables
2 parents 14a6a35 + 97dd9fa commit 5830e80

File tree

6 files changed

+50
-9
lines changed

6 files changed

+50
-9
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"dist:pkg": "rimraf bin && pkg --out-path ./bin package.json",
1717
"dev": "NODE_ENV=development npm-run-all build server",
1818
"dev:watch": "nodemon",
19-
"pretty": "prettier --write \"./src/**/*.{js,json,yml,md,vue,css,scss}\"",
19+
"pretty": "prettier --write \"{src,test}/**/*.ts\"",
2020
"server": "node ./dist/start.js",
2121
"test": "node -r esm ./node_modules/.bin/mocha 'test/**/*.js' --recursive "
2222
},

src/api/tables.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Router } from 'express'
2-
import format from 'pg-format'
2+
import format, { ident } from 'pg-format'
33
import { coalesceRowsToArray, toTransaction } from '../lib/helpers'
44
import { RunQuery } from '../lib/connectionPool'
55
import { DEFAULT_SYSTEM_SCHEMAS } from '../lib/constants'
@@ -233,16 +233,35 @@ const selectSingleByName = (
233233
const createTableSqlize = ({
234234
name,
235235
schema = 'public',
236+
replica_identity,
237+
replica_identity_index,
236238
comment,
237239
}: {
238240
name: string
239241
schema?: string
242+
replica_identity?: 'DEFAULT' | 'INDEX' | 'FULL' | 'NOTHING'
243+
replica_identity_index?: string
240244
comment?: string
241245
}) => {
242246
const tableSql = format('CREATE TABLE IF NOT EXISTS %I.%I ();', schema, name)
247+
let replicaSql: string
248+
if (replica_identity === undefined) {
249+
replicaSql = ''
250+
} else if (replica_identity === 'INDEX') {
251+
replicaSql = `ALTER TABLE ${ident(schema)}.${ident(
252+
name
253+
)} REPLICA IDENTITY USING INDEX ${replica_identity_index};`
254+
} else {
255+
replicaSql = `ALTER TABLE ${ident(schema)}.${ident(name)} REPLICA IDENTITY ${replica_identity};`
256+
}
243257
const commentSql =
244258
comment === undefined ? '' : format('COMMENT ON TABLE %I.%I IS %L;', schema, name, comment)
245-
return `${tableSql} ${commentSql}`
259+
return `
260+
BEGIN;
261+
${tableSql}
262+
${replicaSql}
263+
${commentSql}
264+
COMMIT;`
246265
}
247266
const alterTableName = (previousName: string, newName: string, schema: string) => {
248267
return format('ALTER TABLE %I.%I RENAME TO %I;', schema, previousName, newName)
@@ -252,15 +271,19 @@ const alterTableSql = ({
252271
name,
253272
rls_enabled,
254273
rls_forced,
274+
replica_identity,
275+
replica_identity_index,
255276
comment,
256277
}: {
257278
schema?: string
258279
name: string
259280
rls_enabled?: boolean
260281
rls_forced?: boolean
282+
replica_identity?: 'DEFAULT' | 'INDEX' | 'FULL' | 'NOTHING'
283+
replica_identity_index?: string
261284
comment?: string
262285
}) => {
263-
let alter = format('ALTER table %I.%I', schema, name)
286+
let alter = format('ALTER TABLE %I.%I', schema, name)
264287
let enableRls = ''
265288
if (rls_enabled !== undefined) {
266289
let enable = `${alter} ENABLE ROW LEVEL SECURITY;`
@@ -273,13 +296,23 @@ const alterTableSql = ({
273296
let disable = `${alter} NO FORCE ROW LEVEL SECURITY;`
274297
forceRls = rls_forced ? enable : disable
275298
}
299+
let replicaSql: string
300+
if (replica_identity === undefined) {
301+
replicaSql = ''
302+
} else if (replica_identity === 'INDEX') {
303+
replicaSql = `${alter} REPLICA IDENTITY USING INDEX ${replica_identity_index};`
304+
} else {
305+
replicaSql = `${alter} REPLICA IDENTITY ${replica_identity};`
306+
}
276307
const commentSql =
277308
comment === undefined ? '' : format('COMMENT ON TABLE %I.%I IS %L;', schema, name, comment)
278309
return `
310+
BEGIN;
279311
${enableRls}
280312
${forceRls}
313+
${replicaSql}
281314
${commentSql}
282-
`.trim()
315+
COMMIT;`
283316
}
284317
const dropTableSql = (schema: string, name: string, cascade: boolean) => {
285318
return format(`DROP TABLE %I.%I ${cascade ? 'CASCADE' : 'RESTRICT'};`, schema, name)

src/lib/connectionPool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { SQLStatement } from 'sql-template-strings'
66
pg.types.setTypeParser(20, 'text', parseInt)
77
const { Pool } = pg
88

9-
export const RunQuery = async (connectionString: any, sql: string|SQLStatement) => {
9+
export const RunQuery = async (connectionString: any, sql: string | SQLStatement) => {
1010
const pool = new Pool({ connectionString })
1111
try {
1212
const results = await pool.query(sql)

src/lib/interfaces.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ export namespace Tables {
6565
is_typed: boolean
6666
bytes: number
6767
size: string
68-
rls_enabled: boolean // determines where RLS has been turned on
69-
rls_forced: boolean // determines whether RLS should be forced on the table owner
68+
rls_enabled: boolean // determines where RLS has been turned on
69+
rls_forced: boolean // determines whether RLS should be forced on the table owner
7070

7171
// pg_stat_all_tables columns
7272
sequential_scans: number

src/lib/sql/tables.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ SELECT
66
is_insertable_into,
77
relrowsecurity::bool as rls_enabled,
88
relforcerowsecurity as rls_forced,
9+
CASE WHEN relreplident = 'd' THEN 'DEFAULT'
10+
WHEN relreplident = 'i' THEN 'INDEX'
11+
WHEN relreplident = 'f' THEN 'FULL'
12+
ELSE 'NOTHING'
13+
END AS replica_identity,
914
is_typed,
1015
pg_total_relation_size(format('%I.%I', table_schema, table_name))::bigint AS bytes,
1116
pg_size_pretty(

test/integration/index.spec.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,9 @@ describe('/tables', async () => {
250250
assert.equal(true, !!included)
251251
})
252252
it('POST /tables should create a table', async () => {
253-
const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'test', comment: 'foo' })
253+
const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'test', replica_identity: 'FULL', comment: 'foo' })
254254
assert.equal(`${newTable.schema}.${newTable.name}`, 'public.test')
255+
assert.equal(newTable.replica_identity, 'FULL')
255256
assert.equal(newTable.comment, 'foo')
256257

257258
const { data: tables } = await axios.get(`${URL}/tables`)
@@ -268,11 +269,13 @@ describe('/tables', async () => {
268269
name: 'test a',
269270
rls_enabled: true,
270271
rls_forced: true,
272+
replica_identity: 'NOTHING',
271273
comment: 'foo',
272274
})
273275
assert.equal(updatedTable.name, `test a`)
274276
assert.equal(updatedTable.rls_enabled, true)
275277
assert.equal(updatedTable.rls_forced, true)
278+
assert.equal(updatedTable.replica_identity, 'NOTHING')
276279
assert.equal(updatedTable.comment, 'foo')
277280
await axios.delete(`${URL}/tables/${newTable.id}`)
278281
})

0 commit comments

Comments
 (0)