Skip to content

Conversation

@Nosfistis
Copy link

Description of change

Fix a postgres issue with .dropView(myView) that failed when the view to be dropped is not the default one.
Fix a postgres issue with .dropView(myView) when used on a view created with .createView(myView), or .createedView(myView, false), i.e. created with no metadata set in typeorm_metadata.

Closes #11209

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

@qodo-free-for-open-source-projects

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 Security concerns

SQL injection:
The query construction at lines 3216 and 3256 uses string interpolation with schema and tableName variables (e.g., '${schema}' and '${tableName}'). While these values come from driver.parseTableName(), if the parsing doesn't properly sanitize input, this could be vulnerable to SQL injection. The values should be parameterized or properly escaped rather than directly interpolated into the SQL string.

⚡ Recommended focus areas for review

Incomplete SQL Query

The query construction at line 3256 appears incomplete. It starts with WHERE clause but has no FROM clause condition before it, and the viewsCondition is added with AND without a preceding condition. This will result in invalid SQL syntax like 'WHERE AND (...)' when viewsCondition is present.

const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
        COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
        "relname"                                     AS "name",
        COALESCE("v"."definition", "mv"."definition") AS "value"
    FROM "pg_class" "i"
            LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
            LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
    WHERE ${viewsCondition ? `AND (${viewsCondition})` : ""}`
Schema Filtering Logic

The viewsCondition at line 3216 uses COALESCE for schema matching but references table alias 'i' for relname, while the query at line 3253 uses 'i' as the main table. The join conditions between pg_class, pg_views, and pg_matviews need validation to ensure proper schema filtering works for both regular and materialized views.

    return `(COALESCE("v"."schemaname", "mv"."schemaname") = '${schema}' AND "i"."relname" = '${tableName}')`
})
Duplicate Class Name

The class is named 'FooView' but the file is 'OtherFooView.ts', and there's already a FooView class in another file. This will cause naming conflicts when both entities are imported together.

export class FooView {
    @ViewColumn()

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Jan 2, 2026

PR Code Suggestions ✨

Latest suggestions up to d5ca61a

CategorySuggestion                                                                                                                                    Impact
Security
Escape user input in SQL

Escape the schema and tableName variables using this.driver.escape() to prevent
a SQL injection vulnerability.

src/driver/postgres/PostgresQueryRunner.ts [3282]

-return `(COALESCE("v"."schemaname", "mv"."schemaname") = '${schema}' AND "i"."relname" = '${tableName}')`
+return `(COALESCE("v"."schemaname", "mv"."schemaname") = ${this.driver.escape(schema)} AND "i"."relname" = ${this.driver.escape(tableName)})`
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical SQL injection vulnerability where schema and tableName from user-provided viewNames are not escaped before being used in a raw SQL query, and provides a correct fix.

High
Possible issue
Add schema matching in joins

Add schema matching conditions to the LEFT JOIN clauses for pg_views and
pg_matviews to prevent incorrect mappings when views with the same name exist in
different schemas.

src/driver/postgres/PostgresQueryRunner.ts [3315-3322]

 const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
-                LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
-                LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
+                LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname" AND "v"."schemaname" = (SELECT "nspname" FROM "pg_namespace" WHERE "oid" = "i"."relnamespace")
+                LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname" AND "mv"."schemaname" = (SELECT "nspname" FROM "pg_namespace" WHERE "oid" = "i"."relnamespace")
         WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential bug where views with the same name in different schemas could be mismatched. Adding a schema condition to the JOINs would make the query more robust and prevent incorrect view lookups.

Medium
  • More

Previous suggestions

Suggestions up to commit ef7aa92
CategorySuggestion                                                                                                                                    Impact
Security
Escape user input in SQL

Escape the schema and tableName variables in the SQL query fragment to prevent a
potential SQL injection vulnerability.

src/driver/postgres/PostgresQueryRunner.ts [3216]

-return `(COALESCE("v"."schemaname", "mv"."schemaname") = '${schema}' AND "i"."relname" = '${tableName}')`
+return `(COALESCE("v"."schemaname", "mv"."schemaname") = ${this.driver.escape(schema)} AND "i"."relname" = ${this.driver.escape(tableName)})`
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical SQL injection vulnerability by pointing out that schema and tableName are interpolated into a query fragment without escaping.

High
Suggestions up to commit 769eed4
CategorySuggestion                                                                                                                                    Impact
Security
Escape SQL identifiers to prevent injection

Escape the schema and tableName variables using this.driver.escape() before
interpolating them into the SQL query to prevent a SQL injection vulnerability.

src/driver/postgres/PostgresQueryRunner.ts [3282]

-return `(COALESCE("v"."schemaname", "mv"."schemaname") = '${schema}' AND "i"."relname" = '${tableName}')`
+return `(COALESCE("v"."schemaname", "mv"."schemaname") = ${this.driver.escape(schema)} AND "i"."relname" = ${this.driver.escape(tableName)})`
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a critical SQL injection vulnerability where unescaped schema and tableName values are interpolated into the query, and provides a valid fix using an escape function.

Medium
Prevent SQL injection vulnerability

To prevent potential SQL injection, use parameterized queries for
MetadataTableType values instead of string interpolation in the loadViews query.

src/driver/postgres/PostgresQueryRunner.ts [3315-3322]

-const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
+const query = `SELECT CASE WHEN "relkind" = 'm' THEN $1 ELSE $2 END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
                 LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
                 LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
         WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
+// Execute with parameters: [MetadataTableType.MATERIALIZED_VIEW, MetadataTableType.VIEW]
Suggestion importance[1-10]: 2

__

Why: The suggestion incorrectly identifies MetadataTableType values as a SQL injection vector, as they are internal constants, not user input. While viewsCondition is a valid concern, the proposed fix does not address it.

Low
Possible issue
Add schema matching to JOIN clauses

Add schema matching to the JOIN conditions for pg_views and pg_matviews to
prevent incorrect results when views with the same name exist in multiple
schemas.

src/driver/postgres/PostgresQueryRunner.ts [3315-3322]

 const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
-                LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
-                LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
+                INNER JOIN "pg_namespace" "n" ON "n"."oid" = "i"."relnamespace"
+                LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname" AND "v"."schemaname" = "n"."nspname"
+                LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname" AND "mv"."schemaname" = "n"."nspname"
         WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential bug where views with the same name in different schemas could be mismatched, and the proposed fix correctly resolves this by adding schema matching to the JOIN conditions.

Medium
Suggestions up to commit 35ae94d
CategorySuggestion                                                                                                                                    Impact
Security
Prevent SQL injection vulnerability

Refactor the loadViews query to use query parameters for schema and tableName to
prevent a potential SQL injection vulnerability.

src/driver/postgres/PostgresQueryRunner.ts [3249-3256]

+const viewParams: any[] = []
+const viewsCondition = viewNames.length === 0
+    ? "1=1"
+    : viewNames
+          .map((tableName) => this.driver.parseTableName(tableName))
+          .map(({ schema, tableName }, idx) => {
+              if (!schema) {
+                  schema = this.driver.options.schema || currentSchema
+              }
+              const schemaParam = `$${viewParams.length + 1}`
+              const tableParam = `$${viewParams.length + 2}`
+              viewParams.push(schema, tableName)
+              return `(COALESCE("v"."schemaname", "mv"."schemaname") = ${schemaParam} AND "i"."relname" = ${tableParam})`
+          })
+          .join(" OR ")
+
 const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
                 LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
                 LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
         WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
 
+const dbViews = await this.query(query, viewParams)
+
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a SQL injection vulnerability by interpolating user-controllable schema and tableName values into the query and proposes the correct fix of using parameterized queries.

High
Possible issue
Add missing relkind filter condition

Add a WHERE "i"."relkind" IN ('v', 'm') clause to the loadViews query to ensure
it only selects views and materialized views.

src/driver/postgres/PostgresQueryRunner.ts [3249-3256]

 const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
                 LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
                 LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
-        WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
+        WHERE "i"."relkind" IN ('v', 'm') AND ${viewsCondition ? `(${viewsCondition})` : "1=1"}`
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the query is missing a filter for relkind, which could cause it to process incorrect relation types from pg_class, leading to incorrect behavior.

Medium
Suggestions up to commit 009295d
CategorySuggestion                                                                                                                                    Impact
Security
Prevent SQL injection in query

To prevent SQL injection, sanitize the schema and tableName variables using
this.driver.escape() before interpolating them into the query string.

src/driver/postgres/PostgresQueryRunner.ts [3216]

-return `(COALESCE("v"."schemaname", "mv"."schemaname") = '${schema}' AND "i"."relname" = '${tableName}')`
+return `(COALESCE("v"."schemaname", "mv"."schemaname") = ${this.driver.escape(schema)} AND "i"."relname" = ${this.driver.escape(tableName)})`
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical SQL injection vulnerability by pointing out that schema and tableName are interpolated directly into the query without sanitization.

High
Possible issue
Filter by relkind for views

Add a WHERE "i"."relkind" IN ('v', 'm') clause to the query on pg_class to
ensure only views and materialized views are selected, preventing a bug when no
view names are provided.

src/driver/postgres/PostgresQueryRunner.ts [3249-3256]

 const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
                 LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
                 LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
-        WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
+        WHERE "i"."relkind" IN ('v', 'm') ${viewsCondition ? `AND (${viewsCondition})` : ""}`
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where if viewNames is empty, the query would attempt to load all objects from pg_class as views, which is incorrect and would cause errors.

High
Suggestions up to commit 0709342
CategorySuggestion                                                                                                                                    Impact
Security
Fix SQL injection in view condition

Fix a SQL injection vulnerability by escaping the schema and tableName variables
before they are interpolated into the viewsCondition string. Use
this.driver.escape() for proper escaping.

src/driver/postgres/PostgresQueryRunner.ts [3216]

-return `(COALESCE("v"."schemaname", "mv"."schemaname") = '${schema}' AND "i"."relname" = '${tableName}')`
+return `(COALESCE("v"."schemaname", "mv"."schemaname") = ${this.driver.escape(schema)} AND "i"."relname" = ${this.driver.escape(tableName)})`
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical SQL injection vulnerability. The schema and tableName variables, which can be derived from user input, are directly interpolated into the query without escaping, posing a significant security risk.

High
Prevent potential SQL injection vulnerability

Use parameterized queries instead of string interpolation for MetadataTableType
constants to prevent potential SQL injection. This involves passing the
constants as parameters to the this.query method.

src/driver/postgres/PostgresQueryRunner.ts [3249-3256]

-const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
+const query = `SELECT CASE WHEN "relkind" = 'm' THEN $1 ELSE $2 END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
                 LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
                 LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
         WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
 
+const dbViews = await this.query(query, [MetadataTableType.MATERIALIZED_VIEW, MetadataTableType.VIEW])
+
Suggestion importance[1-10]: 3

__

Why: The suggestion correctly points out a best practice for security by using parameterized queries. However, the interpolated values are internal, hardcoded enum members, making the actual risk of SQL injection negligible.

Low
Possible issue
Handle empty WHERE clause condition

Prevent a SQL syntax error by handling the case where viewsCondition is empty.
Restore the 1=1 fallback in the WHERE clause for when no view names are
specified.

src/driver/postgres/PostgresQueryRunner.ts [3249-3256]

 const query = `SELECT CASE WHEN "relkind" = 'm' THEN '${MetadataTableType.MATERIALIZED_VIEW}' ELSE '${MetadataTableType.VIEW}' END AS "type",
             COALESCE(v."schemaname", "mv"."schemaname")   AS "schema",
             "relname"                                     AS "name",
             COALESCE("v"."definition", "mv"."definition") AS "value"
         FROM "pg_class" "i"
                 LEFT JOIN "pg_views" "v" ON "v"."viewname" = "i"."relname"
                 LEFT JOIN "pg_matviews" "mv" ON "mv"."matviewname" = "i"."relname"
-        WHERE ${viewsCondition ? `(${viewsCondition})` : ""}`
+        WHERE ${viewsCondition || "1=1"}`
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a regression that would cause a SQL syntax error when no view names are provided. The fix restores the previous logic of using 1=1 as a fallback, ensuring the query remains valid in all cases.

Medium

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Jan 2, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Fix SQL injection vulnerability
Suggestion Impact:The commit addresses the SQL injection vulnerability identified in the suggestion by using PostgreSQL's quote_ident() function to properly escape schema and table names in dynamically constructed SQL queries. While the implementation differs from the suggested parameterized query approach (using $1, $2 placeholders), it achieves the same security goal by properly escaping identifiers. The changes appear in lines 286, 294, 304, 313, 322, and 331 where quote_ident() is used to wrap schema and table names instead of direct string concatenation with double quotes.

code diff:

+                `SELECT 'DROP VIEW IF EXISTS ' || quote_ident(schemaname) || '.' || quote_ident(viewname) || ' CASCADE;' as "query" ` +
                 `FROM "pg_views" WHERE "schemaname" IN (${schemaNamesString}) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')`
             const dropViewQueries: ObjectLiteral[] =
                 await this.query(selectViewDropsQuery)
@@ -3146,7 +3212,7 @@
             // Note: materialized views introduced in Postgres 9.3
             if (DriverUtils.isReleaseVersionOrGreater(this.driver, "9.3")) {
                 const selectMatViewDropsQuery =
-                    `SELECT 'DROP MATERIALIZED VIEW IF EXISTS "' || schemaname || '"."' || matviewname || '" CASCADE;' as "query" ` +
+                    `SELECT 'DROP MATERIALIZED VIEW IF EXISTS ' || quote_ident(schemaname) || '.' || quote_ident(matviewname) || ' CASCADE;' as "query" ` +
                     `FROM "pg_matviews" WHERE "schemaname" IN (${schemaNamesString})`
                 const dropMatViewQueries: ObjectLiteral[] = await this.query(
                     selectMatViewDropsQuery,
@@ -3160,7 +3226,7 @@
             // TODO generalize this as this.driver.ignoreTables
 
             // drop tables
-            const selectTableDropsQuery = `SELECT 'DROP TABLE IF EXISTS "' || schemaname || '"."' || tablename || '" CASCADE;' as "query" FROM "pg_tables" WHERE "schemaname" IN (${schemaNamesString}) AND "tablename" NOT IN ('spatial_ref_sys')`
+            const selectTableDropsQuery = `SELECT 'DROP TABLE IF EXISTS ' || quote_ident(schemaname) || '.' || quote_ident(tablename) || ' CASCADE;' as "query" FROM "pg_tables" WHERE "schemaname" IN (${schemaNamesString}) AND "tablename" NOT IN ('spatial_ref_sys')`
             const dropTableQueries: ObjectLiteral[] = await this.query(
                 selectTableDropsQuery,
             )
@@ -3324,7 +3390,7 @@
         }[] = []
 
         if (!tableNames) {
-            const tablesSql = `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables"`
+            const tablesSql = `SELECT "table_schema", "table_name", obj_description((quote_ident("table_schema") || '.' || quote_ident("table_name"))::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables"`
             dbTables.push(...(await this.query(tablesSql)))
         } else {
             const tablesCondition = tableNames
@@ -3337,7 +3403,7 @@
                 .join(" OR ")
 
             const tablesSql =
-                `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ` +
+                `SELECT "table_schema", "table_name", obj_description((quote_ident("table_schema") || '.' || quote_ident("table_name"))::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ` +
                 tablesCondition
             dbTables.push(...(await this.query(tablesSql)))
         }
@@ -3358,7 +3424,7 @@
             })
             .join(" OR ")
         const columnsSql =
-            `SELECT columns.*, pg_catalog.col_description(('"' || table_catalog || '"."' || table_schema || '"."' || table_name || '"')::regclass::oid, ordinal_position) AS description, ` +
+            `SELECT columns.*, pg_catalog.col_description((quote_ident(table_catalog) || '.' || quote_ident(table_schema) || '.' || quote_ident(table_name))::regclass::oid, ordinal_position) AS description, ` +

Fix a SQL injection vulnerability by using parameterized queries instead of
directly concatenating schema and tableName variables into the SQL query.

src/driver/postgres/PostgresQueryRunner.ts [3205-3218]

+const parameters: string[] = []
 const viewsCondition =
     viewNames.length === 0
         ? "1=1"
         : viewNames
-              .map((tableName) => this.driver.parseTableName(tableName))
-              .map(({ schema, tableName }) => {
-                  if (!schema) {
-                      schema =
-                          this.driver.options.schema || currentSchema
-                  }
+                .map((tableName) => this.driver.parseTableName(tableName))
+                .map(({ schema, tableName }) => {
+                    if (!schema) {
+                        schema =
+                            this.driver.options.schema || currentSchema
+                    }
+                    parameters.push(schema, tableName)
+                    const schemaPlaceholder = `$${parameters.length - 1}`
+                    const tablePlaceholder = `$${parameters.length}`
 
-                  return `(COALESCE("v"."schemaname", "mv"."schemaname") = '${schema}' AND "i"."relname" = '${tableName}')`
-              })
-              .join(" OR ")
+                    return `(COALESCE("v"."schemaname", "mv"."schemaname") = ${schemaPlaceholder} AND "i"."relname" = ${tablePlaceholder})`
+                })
+                .join(" OR ")

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical SQL injection vulnerability in the PR's changes and provides a valid fix using parameterized queries.

High
High-level
Refactor to avoid unintended side-effects

The modification to loadViews fixes dropView but introduces a risk for the
schema synchronization feature. A new, dedicated method should be created for
dropView to query system catalogs, while loadViews should retain its original
behavior to prevent unintended view deletions during synchronization.

Examples:

src/driver/postgres/PostgresQueryRunner.ts [3194-3260]
    protected async loadViews(viewNames?: string[]): Promise<View[]> {
        const hasTable = await this.hasTable(this.getTypeormMetadataTableName())

        if (!hasTable) return []

        if (!viewNames) {
            viewNames = []
        }

        const currentDatabase = await this.getCurrentDatabase()

 ... (clipped 57 lines)

Solution Walkthrough:

Before:

// src/driver/postgres/PostgresQueryRunner.ts

async dropView(target: View | string): Promise<void> {
    // ...
    // getCachedView calls loadViews
    const view = await this.getCachedView(viewName);
    // ...
}

protected async loadViews(viewNames?: string[]): Promise<View[]> {
    // This now queries system catalogs (pg_class, pg_views).
    // It is used by both dropView and schema synchronization.
    const query = `SELECT ... FROM "pg_class" ...`;
    const dbViews = await this.query(query);
    return dbViews.map(/* ... */);
}

After:

// src/driver/postgres/PostgresQueryRunner.ts

async dropView(target: View | string): Promise<void> {
    // ...
    // This now calls a new, specialized method.
    const view = await this.loadViewByName(viewName);
    // ...
}

// New method to load a single view from system catalogs for dropping.
protected async loadViewByName(viewName: string): Promise<View> {
    const query = `SELECT ... FROM "pg_class" ... WHERE "relname" = ...`;
    // ... logic to build and return a View object
}

// loadViews is reverted to its original, safer implementation for schema sync.
protected async loadViews(viewNames?: string[]): Promise<View[]> {
    // Queries the typeorm_metadata table.
    const query = `SELECT * FROM ${this.getTypeormMetadataTableName()} ...`;
    const dbViews = await this.query(query);
    return dbViews.map(/* ... */);
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical and potentially destructive side-effect where schema synchronization could drop manually-created views, which is a major design flaw introduced by the PR.

High
  • More

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 2, 2026

commit: d5ca61a

@Nosfistis Nosfistis changed the title Bugfix/drop view fix(postgres): drop views with no metadata Jan 2, 2026
@Nosfistis Nosfistis force-pushed the bugfix/drop-view branch 2 times, most recently from 8a1df18 to 404e9fa Compare January 4, 2026 00:02
Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Nosfistis for your help.
Few tests are failing can you verify them please?

@Nosfistis Nosfistis force-pushed the bugfix/drop-view branch 2 times, most recently from c4a6934 to eb7c47c Compare January 5, 2026 00:47
@Nosfistis
Copy link
Author

Apologies @gioboa as I had troubles running the tests locally. It should pass now.

Use the schema name as well when dropping a view, where the view is passed as a View object.

Fixes typeorm#11209
@Nosfistis
Copy link
Author

@gioboa I do not know why the test fails. I cannot run the CI docker container locally, as it is private. Running the tests in a postgres docker image with vector & postgis installed they pass. Could we run the tests with query logs?

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw tests are running on your personal repo too, e.g. here
you can add few logs to debug the tests.

@Nosfistis
Copy link
Author

Nosfistis commented Jan 5, 2026

I found out what causes the crash. While trying to emulate the behaviour of typeorm's metadata table, I set the loaded view expression to the definition stored in pg_views or pg_matviews. However, this string is not exactly the same as the one provided in the view metadata. I am not sure how this can be tackled yet. This causes the view to drop, and then the synchronize attempts to delete the index which is already deleted.

It might be that storing metadata in the typeorm_metadata table should be mandatory. Otherwise, since recreating a view is harmless (unless other views depend on it), the view could be recreated each time, but the index drop should check if it exists beforehand.

Drop views that were created with no metadata

Fixes typeorm#11209
@Nosfistis
Copy link
Author

@gioboa The issue is fixed, however CI seems not to be working.

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to trigger it manually 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Postgres queryRunner.dropView does not take into account schema

2 participants