kristiandupont / schemalint

Lint database schemas

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Linting against an empty schema causes confusing error

orangain opened this issue · comments

Details

When running schemalint against an empty schema, the following error TypeError: Cannot read properties of undefined (reading 'tables') occurs. I think this error is confusing for the first time user of the tool.

% npx schemalint
schema-lint
Connecting to postgres on localhost
TypeError: Cannot read properties of undefined (reading 'tables')
    at Object.process (/Users/orange/.npm/_npx/cede960380131524/node_modules/schemalint/build/rules/nameCasing.js:37:22)
    at processDatabase (/Users/orange/.npm/_npx/cede960380131524/node_modules/schemalint/build/engine.js:96:42)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async main (/Users/orange/.npm/_npx/cede960380131524/node_modules/schemalint/build/cli.js:60:26)

The configuration is as follows.

module.exports = {
  connection: {
    host: "localhost",
    user: "postgres",
    password: "postgres",
    database: "postgres",
    charset: "utf8",
  },

  rules: {
    "name-casing": ["error", "snake"],
    "name-inflection": ["error", "singular"],
    "prefer-jsonb-to-json": ["error"],
    "prefer-text-to-varchar": ["error"],
  },

  schemas: [{ name: "public" }],
};

At this time, the public schema does exist, but there is no table in it.

postgres=# \dn
      List of schemas
  Name  |       Owner
--------+-------------------
 public | pg_database_owner
(1 row)

postgres=# SELECT current_schema();
 current_schema
----------------
 public
(1 row)

postgres=# \dt
Did not find any relations.

Expected behavior:

The schemalint command should not cause an error when executed against an empty schema. It would be better to exit successfully because no tables violate the rules. Another option is to display an warning message like "No tables found in the schema" instead of an error.

Version:

% npx schemalint --version
1.0.7

By the way, schemalint is a very powerful tool for maintaining the quality of the database schema. Thank you for creating such a great tool!

Checklist
  • Modify src/rules/nameCasing.ts7815a3e Edit
  • Running GitHub Actions for src/rules/nameCasing.tsEdit
  • Modify src/engine.js56a4c85 Edit
  • Running GitHub Actions for src/engine.jsEdit

🚀 Here's the PR! #330

See Sweep's progress at the progress dashboard!
Sweep Basic Tier: I'm using GPT-4. You have 5 GPT-4 tickets left for the month and 3 for the day. (tracking ID: 70b78b7903)

For more GPT-4 tickets, visit our payment portal. For a one week free trial, try Sweep Pro (unlimited GPT-4 tickets).

Tip

I can email you next time I complete a pull request if you set up your email here!


Actions (click)

  • ↻ Restart Sweep

GitHub Actions✓

Here are the GitHub Actions logs prior to making any changes:

Sandbox logs for 377f745
Checking src/rules/nameCasing.ts for syntax errors... ✅ src/rules/nameCasing.ts has no syntax errors! 1/1 ✓
Checking src/rules/nameCasing.ts for syntax errors...
✅ src/rules/nameCasing.ts has no syntax errors!

Sandbox passed on the latest main, so sandbox checks will be enabled for this issue.


Step 1: 🔎 Searching

I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.

Some code snippets I think are relevant in decreasing order of relevance (click to expand). If some file is missing from here, you can mention the path in the ticket description.

export const nameCasing: Rule = {
name: "name-casing",
docs: {
description: "Enforce casing style of names",
url: "https://github.com/kristiandupont/schemalint/tree/master/src/rules#name-casing",
},
process({ options, schemaObject, report }) {
const expectedCasing = (options.length > 0 && options[0]) || "snake";
const validator =
(entityType: "table" | "view") =>
({ name: entityName }: TableDetails | ViewDetails) => {
const casing = detectCasing(entityName);
const matches = casing === null || casing === expectedCasing;
if (!matches) {
report({
rule: this.name,
identifier: `${schemaObject.name}.${entityName}`,
message: `The ${entityType} ${entityName} seems to be ${casing}-cased rather than ${expectedCasing}-cased.`,
suggestedMigration: `ALTER ${entityType.toUpperCase()} "${entityName}" RENAME TO "${recase(
casing,
expectedCasing,
entityName,
)}";`,
});
}
};
const columnValidator =
(entityType: "table" | "view") =>
({ name: entityName }: TableDetails | ViewDetails) =>
({ name: columnName }: TableColumn | ViewColumn) => {
const casing = detectCasing(columnName);
const matches = casing === null || casing === expectedCasing;
if (!matches) {
report({
rule: this.name,
identifier: `${schemaObject.name}.${entityName}.${columnName}`,
message: `The column ${columnName} on the ${entityType} ${entityName} seems to be ${casing}-cased rather than ${expectedCasing}-cased.`,
suggestedMigration: `ALTER ${entityType.toUpperCase()} "${entityName}" RENAME COLUMN "${columnName}" TO "${recase(
casing,
expectedCasing,
columnName,
)}";`,
});
}
};
schemaObject.tables.forEach((entity) => {
validator("table")(entity);
entity.columns.forEach(columnValidator("table")(entity));
});
schemaObject.views.forEach((entity) => {
validator("view")(entity);
entity.columns.forEach(columnValidator("view")(entity));
});

schemalint/src/engine.js

Lines 69 to 117 in 377f745

if (i.identifier) {
identifierMatch = identifier === i.identifier;
} else if (i.identifierPattern) {
identifierMatch = new RegExp(i.identifierPattern).test(identifier);
} else {
throw new Error(
`Ignore object is missing an identifier or identifierPattern property: ${JSON.stringify(
i,
)}`,
);
}
return ruleMatch && identifierMatch;
});
const report = createReportFunction(consoleReporter, ignoreMatchers);
const extractedSchemas = await extractSchemas(connection, {
schemas: schemas.map((s) => s.name),
});
for (const schema of schemas) {
const schemaObject = extractedSchemas[schema.name];
const mergedRules = {
...rules,
...schema.rules,
};
for (const ruleKey of keys(mergedRules)) {
if (!(ruleKey in registeredRules)) {
throw new Error(`Unknown rule: "${ruleKey}"`);
}
const [state, ...options] = mergedRules[ruleKey];
if (state === "error") {
registeredRules[ruleKey].process({ schemaObject, report, options });
}
}
}
if (anyIssues) {
if (suggestedMigrations.length > 0) {
console.info("");
console.info("Suggested fix");
for (const sf of suggestedMigrations) console.info(sf);
}
return 1;
}
console.info("No issues detected");


Step 2: ⌨️ Coding

Modify src/rules/nameCasing.ts with contents:
• Add a check before the `forEach` calls on `schemaObject.tables` and `schemaObject.views` to ensure that these properties exist and are arrays.
• If `schemaObject.tables` is not an array, skip the `forEach` call that processes tables.
• If `schemaObject.views` is not an array, skip the `forEach` call that processes views.
• The check can be implemented using an `if` statement that checks `Array.isArray(schemaObject.tables)` and `Array.isArray(schemaObject.views)` respectively.
• This change will prevent the `TypeError` from occurring when the schema does not contain any tables or views.
--- 
+++ 
@@ -53,13 +53,17 @@
           });
         }
       };
-    schemaObject.tables.forEach((entity) => {
+    if (Array.isArray(schemaObject.tables)) {
+        schemaObject.tables.forEach((entity) => {
       validator("table")(entity);
-      entity.columns.forEach(columnValidator("table")(entity));
-    });
-    schemaObject.views.forEach((entity) => {
+          entity.columns.forEach(columnValidator("table")(entity));
+        });
+    }
+    if (Array.isArray(schemaObject.views)) {
+        schemaObject.views.forEach((entity) => {
       validator("view")(entity);
-      entity.columns.forEach(columnValidator("view")(entity));
-    });
+          entity.columns.forEach(columnValidator("view")(entity));
+        });
+    }
   },
 };
  • Running GitHub Actions for src/rules/nameCasing.tsEdit
Check src/rules/nameCasing.ts with contents:

Ran GitHub Actions for 7815a3efd9d793dce4d21f68dd0a2f8a057625ae:

Modify src/engine.js with contents:
• Add a check after extracting schemas with `extractSchemas` to ensure that the `schemaObject` for each schema is not undefined before proceeding with rule processing.
• If `schemaObject` is undefined, log a warning message to the console stating that no tables or views were found in the schema and continue to the next schema without processing rules.
• This change will provide a clear message to the user when a schema is empty and prevent further processing that assumes the presence of tables or views.
--- 
+++ 
@@ -87,7 +87,14 @@
   });
 
   for (const schema of schemas) {
-    const schemaObject = extractedSchemas[schema.name];
+      const schemaObject = extractedSchemas[schema.name];
+
+    if (!schemaObject) {
+      console.info(
+        `${chalk.yellow(schema.name)}: warning : No tables or views were found in the schema. Skipping rules processing.`,
+      );
+      continue;
+    }
 
     const mergedRules = {
       ...rules,
  • Running GitHub Actions for src/engine.jsEdit
Check src/engine.js with contents:

Ran GitHub Actions for 56a4c852b4e6e36425249020aa578e66d27d0c77:


Step 3: 🔁 Code Review

I have finished reviewing the code for completeness. I did not find errors for sweep/linting_against_an_empty_schema_causes_c.


🎉 Latest improvements to Sweep:
  • New dashboard launched for real-time tracking of Sweep issues, covering all stages from search to coding.
  • Integration of OpenAI's latest Assistant API for more efficient and reliable code planning and editing, improving speed by 3x.
  • Use the GitHub issues extension for creating Sweep issues directly from your editor.

💡 To recreate the pull request edit the issue title or description. To tweak the pull request, leave a comment on the pull request.Something wrong? Let us know.

This is an automated message generated by Sweep AI.

@orangain I think this fixes your issue? I was actually surprised that Sweep went in and did this but it looks pretty solid :-)

@kristiandupont The message is now easy to understand. Thank you!

This is the first time I have seen Sweap and I too was surprised that it created a pull request exactly from the content of the issue. However, I am concerned about the following two points.

  1. There are changes made that are not relevant to this issue, but I am not sure if they are correct or not.
  2. There were other ways to address this issue, so I am not sure if this was really a good solution. For example, for an empty schema, one could change extract-pg-schema to return a schema object containing an empty array instead of undefined.

Yeah, those are good points and it's definitely a flaw in extract-pg-schema that I should get fixed :-)