From f73f8f443343bd26118a2d6b62439dcfe750c1e1 Mon Sep 17 00:00:00 2001 From: rg2011 <52279456+rg2011@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:29:29 +0100 Subject: [PATCH] Add support for full search path to postgres integration --- .../src/integration-test/postgres.spec.ts | 72 +++++++++++++++++++ packages/server/src/integrations/postgres.ts | 11 ++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/packages/server/src/integration-test/postgres.spec.ts b/packages/server/src/integration-test/postgres.spec.ts index 67e4fee81c..600566c813 100644 --- a/packages/server/src/integration-test/postgres.spec.ts +++ b/packages/server/src/integration-test/postgres.spec.ts @@ -1118,4 +1118,76 @@ describe("postgres integrations", () => { }) }) }) + + describe("Integration compatibility with postgres search_path", () => { + let client: Client, pathDatasource: Datasource + const schema1 = "test1", + schema2 = "test-2" + + beforeAll(async () => { + const dsConfig = await databaseTestProviders.postgres.getDsConfig() + const dbConfig = dsConfig.config! + + client = new Client(dbConfig) + await client.connect() + await client.query(`CREATE SCHEMA "${schema1}";`) + await client.query(`CREATE SCHEMA "${schema2}";`) + + const pathConfig: any = { + ...dsConfig, + config: { + ...dbConfig, + schema: `${schema1}, ${schema2}`, + }, + } + pathDatasource = await config.api.datasource.create(pathConfig) + }) + + afterAll(async () => { + await client.query(`DROP SCHEMA "${schema1}" CASCADE;`) + await client.query(`DROP SCHEMA "${schema2}" CASCADE;`) + await client.end() + }) + + it("discovers tables from any schema in search path", async () => { + await client.query( + `CREATE TABLE "${schema1}".table1 (id1 SERIAL PRIMARY KEY);` + ) + await client.query( + `CREATE TABLE "${schema2}".table2 (id2 SERIAL PRIMARY KEY);` + ) + const response = await makeRequest("post", "/api/datasources/info", { + datasource: pathDatasource, + }) + expect(response.status).toBe(200) + expect(response.body.tableNames).toBeDefined() + expect(response.body.tableNames).toEqual( + expect.arrayContaining(["table1", "table2"]) + ) + }) + + it("does not mix columns from different tables", async () => { + const repeated_table_name = "table_same_name" + await client.query( + `CREATE TABLE "${schema1}".${repeated_table_name} (id SERIAL PRIMARY KEY, val1 TEXT);` + ) + await client.query( + `CREATE TABLE "${schema2}".${repeated_table_name} (id2 SERIAL PRIMARY KEY, val2 TEXT);` + ) + const response = await makeRequest( + "post", + `/api/datasources/${pathDatasource._id}/schema`, + { + tablesFilter: [repeated_table_name], + } + ) + expect(response.status).toBe(200) + expect( + response.body.datasource.entities[repeated_table_name].schema + ).toBeDefined() + const schema = + response.body.datasource.entities[repeated_table_name].schema + expect(Object.keys(schema).sort()).toEqual(["id", "val1"]) + }) + }) }) diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index de3bf0e59e..e1494ece36 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -159,7 +159,8 @@ class PostgresIntegration extends Sql implements DatasourcePlus { JOIN pg_index ON pg_class.oid = pg_index.indrelid AND pg_index.indisprimary JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = ANY(pg_index.indkey) JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace - WHERE pg_namespace.nspname = '${this.config.schema}'; + WHERE pg_namespace.nspname = ANY(current_schemas(false)) + AND pg_table_is_visible(pg_class.oid); ` ENUM_VALUES = () => ` @@ -219,8 +220,12 @@ class PostgresIntegration extends Sql implements DatasourcePlus { if (!this.config.schema) { this.config.schema = "public" } - await this.client.query(`SET search_path TO "${this.config.schema}"`) - this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'` + const search_path = this.config.schema + .split(",") + .map(item => `"${item.trim()}"`) + await this.client.query(`SET search_path TO ${search_path.join(",")};`) + this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = ANY(current_schemas(false)) + AND pg_table_is_visible(to_regclass(table_schema || '.' || table_name));` this.open = true }