diff --git a/lib/DboBuilder/DboBuilderTypes.ts b/lib/DboBuilder/DboBuilderTypes.ts index 8d152b96..1e9de639 100644 --- a/lib/DboBuilder/DboBuilderTypes.ts +++ b/lib/DboBuilder/DboBuilderTypes.ts @@ -34,7 +34,7 @@ export type TableSchemaColumn = ColumnInfo & { privileges: Partial>; } -export type TableSchema = { +export type TableSchema = Pick & { schema: string; name: string; escaped_identifier: string; diff --git a/lib/DboBuilder/ViewHandler/getInfo.ts b/lib/DboBuilder/ViewHandler/getInfo.ts index 7b8df716..b357fb0c 100644 --- a/lib/DboBuilder/ViewHandler/getInfo.ts +++ b/lib/DboBuilder/ViewHandler/getInfo.ts @@ -15,6 +15,7 @@ export async function getInfo(this: ViewHandler, lang?: string, param2?: any, pa const fileTableName = this.dboBuilder.prostgles?.opts?.fileTable?.tableName; await this._log({ command: "getInfo", localParams, data: { lang }, duration: 0 }); + const allowedFieldsToSelect = this.parseFieldFilter(tableRules?.select?.fields); return { oid: this.tableOrViewInfo.oid, comment: this.tableOrViewInfo.comment, @@ -27,6 +28,10 @@ export async function getInfo(this: ViewHandler, lang?: string, param2?: any, pa fileTableName, dynamicRules: { update: Boolean(tableRules?.update?.dynamicFields?.length) - } + }, + /** + * Only show column groups that are fully allowed to be selected by the user + */ + uniqueColumnGroups: this.tableOrViewInfo.uniqueColumnGroups?.filter(g => !localParams || g.every(c => allowedFieldsToSelect.includes(c))), } } \ No newline at end of file diff --git a/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts b/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts index f53e0bcf..20085696 100644 --- a/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +++ b/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts @@ -1,5 +1,5 @@ import { SQLResult, asName } from "prostgles-types"; -import { omitKeys, tryCatch } from "prostgles-types/dist/util"; +import { omitKeys, tryCatch, tryCatchV2 } from "prostgles-types/dist/util"; import { DboBuilder } from "../DboBuilder/DboBuilder"; import { DBorTx } from "../Prostgles"; import { clone } from "../utils"; @@ -167,6 +167,43 @@ export async function getTablesForSchemaPostgresSQL( if(getFkeys.error !== undefined){ throw getFkeys.error; } + + const uniqueColsReq = await tryCatchV2(async () => { + const res = await t.any(` + select + t.relname as table_name, + (SELECT pnm.nspname FROM pg_catalog.pg_namespace pnm WHERE pnm.oid = i.relnamespace) as table_schema, + i.relname as index_name, + array_agg(a.attname)::_TEXT as column_names + from + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a + where + t.oid = ix.indrelid + and i.oid = ix.indexrelid + and a.attrelid = t.oid + and a.attnum = ANY(ix.indkey) + and t.relkind = 'r' + and ix.indisunique + group by 1,2,3 + order by + t.relname, + i.relname; + `) as { + table_name: string; + table_schema: string; + index_name: string; + column_names: string[]; + }[]; + + return res; + }); + + if(uniqueColsReq.error){ + throw uniqueColsReq.error; + } const badFkey = getFkeys.fkeys!.find(r => r.fcols.includes(null as any)); if(badFkey){ @@ -386,6 +423,8 @@ export async function getTablesForSchemaPostgresSQL( }); table.isHyperTable = getHyperTablesReq.hyperTables?.includes(table.name); + + table.uniqueColumnGroups = uniqueColsReq.data?.filter(r => r.table_name === table.name && r.table_schema === table.schema).map(r => r.column_names); return table; })); diff --git a/package-lock.json b/package-lock.json index c2427369..38bb73cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prostgles-server", - "version": "4.2.170", + "version": "4.2.171", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prostgles-server", - "version": "4.2.170", + "version": "4.2.171", "license": "MIT", "dependencies": { "@aws-sdk/client-ses": "^3.699.0", @@ -28,7 +28,7 @@ "pg": "^8.11.5", "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", - "prostgles-types": "^4.0.109" + "prostgles-types": "^4.0.112" }, "devDependencies": { "@types/express": "^4.17.21", @@ -3626,9 +3626,9 @@ } }, "node_modules/prostgles-types": { - "version": "4.0.109", - "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.109.tgz", - "integrity": "sha512-aK7X58BDeO8Q74hJEV6D9NNTUL1vsoZpqgXwQKemZunuN2dgPh7+9boasqf1dmb1ZhymHpq5iCnipEervC3G2g==", + "version": "4.0.112", + "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.112.tgz", + "integrity": "sha512-/4E0uNK9yll+0aqM8cflTtz2J4ELLBFCE9ipDXmJ8b1MivRvm7T6UTB5BDM9+5IAKP0iafqVoDiCjpI+G654qA==", "license": "MIT" }, "node_modules/punycode": { @@ -6840,9 +6840,9 @@ "dev": true }, "prostgles-types": { - "version": "4.0.109", - "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.109.tgz", - "integrity": "sha512-aK7X58BDeO8Q74hJEV6D9NNTUL1vsoZpqgXwQKemZunuN2dgPh7+9boasqf1dmb1ZhymHpq5iCnipEervC3G2g==" + "version": "4.0.112", + "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.112.tgz", + "integrity": "sha512-/4E0uNK9yll+0aqM8cflTtz2J4ELLBFCE9ipDXmJ8b1MivRvm7T6UTB5BDM9+5IAKP0iafqVoDiCjpI+G654qA==" }, "punycode": { "version": "2.3.1", diff --git a/package.json b/package.json index 408ccb0e..e3db9e39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prostgles-server", - "version": "4.2.170", + "version": "4.2.171", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -54,7 +54,7 @@ "pg": "^8.11.5", "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", - "prostgles-types": "^4.0.109" + "prostgles-types": "^4.0.112" }, "devDependencies": { "@types/express": "^4.17.21", diff --git a/tests/client/package-lock.json b/tests/client/package-lock.json index 60a27083..c02a181e 100644 --- a/tests/client/package-lock.json +++ b/tests/client/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@types/node": "^20.9.2", - "prostgles-client": "^4.0.158", + "prostgles-client": "^4.0.162", "prostgles-types": "^4.0.51", "socket.io-client": "^4.7.5" }, @@ -327,12 +327,12 @@ } }, "node_modules/prostgles-client": { - "version": "4.0.158", - "resolved": "https://registry.npmjs.org/prostgles-client/-/prostgles-client-4.0.158.tgz", - "integrity": "sha512-mmyPEkkRLTReeybsz4u0xVrHXvD5OIyeFqeo4ykU6t55n7tG819CHfoGcHibUVLMW/PLCUj0ZlcOrltqYue5Fw==", + "version": "4.0.162", + "resolved": "https://registry.npmjs.org/prostgles-client/-/prostgles-client-4.0.162.tgz", + "integrity": "sha512-EPajFogZstpFU3+W9HQe8pyWRiBujLqv3dMdoOyNaWf+to3pPSiwCne6CoXdeRPRcmernKtCQuhD3Nn3MLHvBQ==", "license": "MIT", "dependencies": { - "prostgles-types": "^4.0.107" + "prostgles-types": "^4.0.112" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0", @@ -348,9 +348,9 @@ } }, "node_modules/prostgles-types": { - "version": "4.0.107", - "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.107.tgz", - "integrity": "sha512-d25PTzxM6922WfAXzH4dcE6ajtWv5JjNK1F6YFGU9Xl3g+Yi6chAp22UsBZLJCCYusPlEch5n0mBhd6UV/b7GQ==", + "version": "4.0.112", + "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.112.tgz", + "integrity": "sha512-/4E0uNK9yll+0aqM8cflTtz2J4ELLBFCE9ipDXmJ8b1MivRvm7T6UTB5BDM9+5IAKP0iafqVoDiCjpI+G654qA==", "license": "MIT" }, "node_modules/psl": { diff --git a/tests/client/package.json b/tests/client/package.json index e52d0bad..9a520d15 100644 --- a/tests/client/package.json +++ b/tests/client/package.json @@ -13,7 +13,7 @@ "license": "ISC", "dependencies": { "@types/node": "^20.9.2", - "prostgles-client": "^4.0.158", + "prostgles-client": "^4.0.162", "prostgles-types": "^4.0.51", "socket.io-client": "^4.7.5" }, diff --git a/tests/isomorphicQueries.spec.ts b/tests/isomorphicQueries.spec.ts index 0f66ca27..112623f4 100644 --- a/tests/isomorphicQueries.spec.ts +++ b/tests/isomorphicQueries.spec.ts @@ -71,6 +71,31 @@ export const isomorphicQueries = async (db: DBOFullyTyped | DBHandlerClient, log } }); + await test("Table info", async () => { + const { + /** Excluded because their value may vary */ + oid, isFileTable, + ...tableInfo + } = (await db.items.getInfo?.()) ?? {}; + assert.deepStrictEqual(tableInfo, { + "comment": null, + "info": { + "label": "items" + }, + "isView": false, + "hasFiles": false, + "fileTableName": "files", + "dynamicRules": { + "update": false + }, + "uniqueColumnGroups": [ + [ + "id" + ] + ] + }); + }); + await test("Prepare data", async () => { if(!db.sql) throw "db.sql missing"; const res = await db.items.insert!([{ name: "a" }, { name: "a" }, { name: "b" }], { returning: "*" }); diff --git a/tests/server/package-lock.json b/tests/server/package-lock.json index b7c6c815..978b5c0b 100644 --- a/tests/server/package-lock.json +++ b/tests/server/package-lock.json @@ -21,7 +21,7 @@ }, "../..": { "name": "prostgles-server", - "version": "4.2.170", + "version": "4.2.171", "license": "MIT", "dependencies": { "@aws-sdk/client-ses": "^3.699.0", @@ -43,7 +43,7 @@ "pg": "^8.11.5", "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", - "prostgles-types": "^4.0.109" + "prostgles-types": "^4.0.112" }, "devDependencies": { "@types/express": "^4.17.21", @@ -1826,7 +1826,7 @@ "pg": "^8.11.5", "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", - "prostgles-types": "^4.0.109", + "prostgles-types": "^4.0.112", "socket.io": "^4.8.1", "typescript": "^5.3.3" }