Skip to content

Commit

Permalink
fix: Substitution 'node-sql-parser' with a forked version until April…
Browse files Browse the repository at this point in the history
… 1st (Next Release) (#597)

A temporary change in our codebase. Replacing the usage of
node-sql-parser with kevin-node-sql-parser until April 1st. The reason
for this substitution is that the official release of node-sql-parser
lacks a version release for the additional SQL statements required for
our current project needs. In the interim, this forked version addresses
these shortcomings and allows us to incorporate the required SQL
statements. Please note that this is a temporary measure, and we plan to
revert to the official node-sql-parser version after April 1st, once the
required features are officially available.

See last comment for details
  • Loading branch information
Kevin101Zhang authored Mar 20, 2024
1 parent d4337a3 commit 1bccff1
Show file tree
Hide file tree
Showing 9 changed files with 2,394 additions and 2,962 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"graphiql": "3.0.6",
"graphql": "^16.8.1",
"gridjs": "6.0.6",
"kevin-node-sql-parser": "^4.18.1",
"monaco-editor": "^0.45.0",
"near-api-js": "1.1.0",
"near-social-bridge": "^1.4.1",
"next": "^13.5.6",
"node-sql-parser": "^4.10.0",
"prettier": "^2.7.1",
"prettier-plugin-sql": "^0.13.0",
"raw-loader": "^4.0.2",
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/Editor/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,6 @@ const Editor = ({ actionButtonText }) => {
`${primitives}}`,
"file:///node_modules/@near-lake/primitives/index.d.ts"
);

setMonacoMount(true);
}

Expand Down
143 changes: 70 additions & 73 deletions frontend/src/utils/pgSchemaTypeGen.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Parser } from "node-sql-parser";
import { Parser } from "kevin-node-sql-parser";
//todo: remove this import and replace with a with default node-sql-parser on next release

export class PgSchemaTypeGen {
constructor() {
Expand Down Expand Up @@ -52,74 +53,75 @@ export class PgSchemaTypeGen {
}

generateTypes(sqlSchema) {
const schemaSyntaxTree = this.parser.astify(sqlSchema, { database: "Postgresql" });
const dbSchema = {};

const statements = Array.isArray(schemaSyntaxTree) ? schemaSyntaxTree : [schemaSyntaxTree];
// Process each statement in the schema
for (const statement of statements) {
if (statement.type === "create" && statement.keyword === "table") {
// Process CREATE TABLE statements
const tableName = statement.table[0].table;
if (dbSchema.hasOwnProperty(tableName)) {
throw new Error(`Table ${tableName} already exists in schema. Table names must be unique. Quotes are not allowed as a differentiator between table names.`);
}
const schemaSyntaxTree = this.parser.astify(sqlSchema, { database: "Postgresql" });
const dbSchema = {};

const statements = Array.isArray(schemaSyntaxTree) ? schemaSyntaxTree : [schemaSyntaxTree];
// Process each statement in the schema
for (const statement of statements) {
if (statement.type === "create" && statement.keyword === "table") {
// Process CREATE TABLE statements
const tableName = statement.table[0].table;
if (dbSchema.hasOwnProperty(tableName)) {
throw new Error(`Table ${tableName} already exists in schema. Table names must be unique. Quotes are not allowed as a differentiator between table names.`);
}

let columns = {};
for (const columnSpec of statement.create_definitions) {
if (columnSpec.hasOwnProperty("column") && columnSpec.hasOwnProperty("definition")) {
// New Column
this.addColumn(columnSpec, columns);
} else if (columnSpec.hasOwnProperty("constraint") && columnSpec.constraint_type === "primary key") {
// Constraint on existing column
for (const foreignKeyDef of columnSpec.definition) {
columns[foreignKeyDef.column].nullable = false;
}
let columns = {};
for (const columnSpec of statement.create_definitions) {
if (columnSpec.hasOwnProperty("column") && columnSpec.hasOwnProperty("definition")) {
// New Column
this.addColumn(columnSpec, columns);
} else if (columnSpec.hasOwnProperty("constraint") && columnSpec.constraint_type == "primary key") {
// Constraint on existing column
for (const foreignKeyDef of columnSpec.definition) {
columns[foreignKeyDef.column.expr.value].nullable = false;
}
}
dbSchema[tableName] = columns;
} else if (statement.type === "alter") {
// Process ALTER TABLE statements
const tableName = statement.table[0].table;
for (const alterSpec of statement.expr) {
switch (alterSpec.action) {
case "add":
switch (alterSpec.resource) {
case "column": // Add column to table
this.addColumn(alterSpec, dbSchema[tableName]);
break;
case "constraint": // Add constraint to column(s) (Only PRIMARY KEY constraint impacts output types)
const newConstraint = alterSpec.create_definitions;
if (newConstraint.constraint_type === "primary key") {
for (const foreignKeyDef of newConstraint.definition) {
dbSchema[tableName][foreignKeyDef.column].nullable = false;
}
}
dbSchema[tableName] = columns;
} else if (statement.type === "alter") {
// Process ALTER TABLE statements
const tableName = statement.table[0].table;
for (const alterSpec of statement.expr) {
switch (alterSpec.action) {
case "add":
switch (alterSpec.resource) {
case "column": // Add column to table
this.addColumn(alterSpec, dbSchema[tableName]);
break;
case "constraint": // Add constraint to column(s) (Only PRIMARY KEY constraint impacts output types)
const newConstraint = alterSpec.create_definitions;
if (newConstraint.constraint_type == "primary key") {
for (const foreignKeyDef of newConstraint.definition) {
dbSchema[tableName][foreignKeyDef.column].nullable = false;
}
break;
}
break;
case "drop": // Can only drop column for now
delete dbSchema[tableName][alterSpec.column.column];
break;
}
}
break;
}
break;
case "drop": // Can only drop column for now
delete dbSchema[tableName][alterSpec.column.column];
break;
}
}
}
}

const tsTypes = this.generateTypeScriptDefinitions(dbSchema);
console.log(`Types successfully generated`);
return tsTypes;
const tsTypes = this.generateTypeScriptDefinitions(dbSchema);
console.log(`Types successfully generated`);
return tsTypes;
}

addColumn(columnDef, columns) {
const columnName = columnDef.column.column;
const columnName = columnDef.column.column.expr.value;
const columnType = this.getTypescriptType(columnDef.definition.dataType);
const nullable = this.getNullableStatus(columnDef);
const required = this.getRequiredStatus(columnDef, nullable);
if (columns.hasOwnProperty(columnName)) {
console.warn(`Column ${columnName} already exists in table. Skipping.`);
return;
}

columns[columnName] = {
type: columnType,
nullable: nullable,
Expand All @@ -130,10 +132,10 @@ export class PgSchemaTypeGen {
getNullableStatus(columnDef) {
const isPrimaryKey =
columnDef.hasOwnProperty("unique_or_primary") &&
columnDef.unique_or_primary === "primary key";
columnDef.unique_or_primary == "primary key";
const isNullable =
columnDef.hasOwnProperty("nullable") &&
columnDef.nullable.value === "not null";
columnDef.nullable.value == "not null";
return isPrimaryKey || isNullable ? false : true;
}

Expand All @@ -159,43 +161,38 @@ export class PgSchemaTypeGen {
// Process each table
for (const [tableName, columns] of Object.entries(schema)) {
let itemDefinition = "";
let partialDefinition = "";
let filterDefinition = "";
let inputDefinition = "";
const sanitizedTableName = this.sanitizeTableName(tableName);
if (tableList.has(sanitizedTableName)) {
throw new Error(`Table '${tableName}' has the same name as another table in the generated types. Special characters are removed to generate context.db methods. Please rename the table.`);
}
tableList.add(sanitizedTableName);
// Create interfaces for strongly typed input and row item
partialDefinition += `declare interface ${sanitizedTableName}Partial {\n`;
itemDefinition += `declare interface ${sanitizedTableName}Item {\n`;
filterDefinition += `declare interface ${sanitizedTableName}Filter {\n`;
for (const [colName, col] of Object.entries(columns)) {
const tsType = col.nullable ? `${col.type} | null` : `${col.type}`
partialDefinition += ` ${colName}?: ${tsType};\n`;
const optional = col.required ? "" : "?";
itemDefinition += ` ${colName}${optional}: ${tsType};\n`;
const conditionType = `${tsType} | ${col.type}[]`;
filterDefinition += ` ${colName}?: ${conditionType};\n`;
inputDefinition += `declare interface ${sanitizedTableName}Input {\n`;
for (const [columnName, columnDetails] of Object.entries(columns)) {
let tsType = columnDetails.nullable ? columnDetails.type + " | null" : columnDetails.type;
const optional = columnDetails.required ? "" : "?";
itemDefinition += ` ${columnName}?: ${tsType};\n`; // Item fields are always optional
inputDefinition += ` ${columnName}${optional}: ${tsType};\n`;
}
itemDefinition += "}\n\n";
partialDefinition += "}\n\n";
filterDefinition += "}\n\n";
inputDefinition += "}\n\n";

// Create type containing column names to be used as a replacement for string[].
const columnNamesDef = `type ${sanitizedTableName}Columns = "${Object.keys(columns).join('" | "')}";\n\n`;

// Add generated types to definitions
tsDefinitions += itemDefinition + partialDefinition + filterDefinition + columnNamesDef;
tsDefinitions += itemDefinition + inputDefinition + columnNamesDef;

// Create context object with correctly formatted methods. Name, input, and output should match actual implementation
contextObject += `
${sanitizedTableName}: {
insert: (values: ${sanitizedTableName}Item | ${sanitizedTableName}Item[]) => Promise<${sanitizedTableName}Item[]>;
select: (where: ${sanitizedTableName}Filter, limit = null) => Promise<${sanitizedTableName}Item[]>;
update: (where: ${sanitizedTableName}Partial, set: ${sanitizedTableName}Partial) => Promise<${sanitizedTableName}Item[]>;
upsert: (values: ${sanitizedTableName}Item | ${sanitizedTableName}Item[], conflictColumns: ${sanitizedTableName}Columns[], updateColumns: ${sanitizedTableName}Columns[]) => Promise<${sanitizedTableName}Item[]>;
delete: (where: ${sanitizedTableName}Filter) => Promise<${sanitizedTableName}Item[]>;
insert: (objectsToInsert: ${sanitizedTableName}Input | ${sanitizedTableName}Input[]) => Promise<${sanitizedTableName}Item[]>;
select: (filterObj: ${sanitizedTableName}Item, limit = null) => Promise<${sanitizedTableName}Item[]>;
update: (filterObj: ${sanitizedTableName}Item, updateObj: ${sanitizedTableName}Item) => Promise<${sanitizedTableName}Item[]>;
upsert: (objectsToInsert: ${sanitizedTableName}Input | ${sanitizedTableName}Input[], conflictColumns: ${sanitizedTableName}Columns[], updateColumns: ${sanitizedTableName}Columns[]) => Promise<${sanitizedTableName}Item[]>;
delete: (filterObj: ${sanitizedTableName}Item) => Promise<${sanitizedTableName}Item[]>;
},`;
}

Expand Down Expand Up @@ -271,4 +268,4 @@ export class PgSchemaTypeGen {
return "any";
}
}
}
}
1 change: 1 addition & 0 deletions frontend/src/utils/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function validateSQLSchema(schema) {
pgSchemaTypeGen.generateTypes(formattedSchema); // Sanity check
return { data: formattedSchema, error: null }
} catch (error) {
console.log(error)
return { data: schema, error: new ValidationError(error.message, TYPE_GENERATION_ERROR_TYPE), location: error.location };
}
}
Expand Down
Loading

0 comments on commit 1bccff1

Please sign in to comment.