Skip to content

Commit

Permalink
Utilize new functions in frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
darunrs committed Mar 27, 2024
1 parent 497a00d commit bc1f339
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 38 deletions.
120 changes: 120 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 27 additions & 15 deletions frontend/src/utils/indexerRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,17 @@ export default class IndexerRunner {
}

buildDatabaseContext (blockHeight, schemaName, schema) {
console.log("I AM (NOT) HERE");
try {
const tables = this.pgSchemaTypeGen.getTableNames(schema);
const tableNameToDefinitionNamesMapping = this.pgSchemaTypeGen.getTableNameToDefinitionNamesMapping(schema);
const tableNames = Array.from(tableNameToDefinitionNamesMapping.keys());
const sanitizedTableNames = new Set();

// Generate and collect methods for each table name
const result = tables.reduce((prev, tableName) => {
const result = tableNames.reduce((prev, tableName) => {
// Generate sanitized table name and ensure no conflict
const sanitizedTableName = this.pgSchemaTypeGen.sanitizeTableName(tableName);
const tableDefinitionNames = tableNameToDefinitionNamesMapping.get(tableName);
if (sanitizedTableNames.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.`);
} else {
Expand All @@ -172,24 +175,32 @@ export default class IndexerRunner {
// Generate context.db methods for table
const funcForTable = {
[`${sanitizedTableName}`]: {
insert: async (objects) => await this.dbOperationLog(blockHeight,
`Inserting the following objects into table ${sanitizedTableName} on schema ${schemaName}`,
objects),
insert: async (rowsToInsert) => await this.dbOperationLog(blockHeight,
`Inserting the following objects into table ${tableDefinitionNames.originalTableName} on schema ${schemaName}`,
rowsToInsert),

select: async (object, limit = null) => await this.dbOperationLog(blockHeight,
`Selecting objects with the following values from table ${sanitizedTableName} on schema ${schemaName} with ${limit === null ? 'no' : limit} limit`,
object),
select: async (whereObj, limit = null) => await this.dbOperationLog(blockHeight,
`Selecting objects with the following values from table ${tableDefinitionNames.originalTableName} on schema ${schemaName} with ${limit === null ? 'no' : limit} limit`,
whereObj),

update: async (whereObj, updateObj) => await this.dbOperationLog(blockHeight,
`Updating objects that match the specified fields with the following values in table ${sanitizedTableName} on schema ${schemaName}`,
{matchingFields: whereObj, fieldsToUpdate: updateObj}),
`Updating objects that match the specified fields with the following values in table ${tableDefinitionNames.originalTableName} on schema ${schemaName}`,
{
matchingFields: whereObj.map(col => tableDefinitionNames.originalColumnNames.get(col) ?? col),
fieldsToUpdate: updateObj.map(col => tableDefinitionNames.originalColumnNames.get(col) ?? col)
}),

upsert: async (objects, conflictColumns, updateColumns) => await this.dbOperationLog(blockHeight,
`Inserting the following objects into table ${sanitizedTableName} on schema ${schemaName}. Conflict on the specified columns will update values in the specified columns`,
{insertObjects: objects, conflictColumns: conflictColumns.join(', '), updateColumns: updateColumns.join(', ')}),
upsert: async (rowsToUpsert, conflictColumns, updateColumns) => await this.dbOperationLog(blockHeight,
`Inserting the following objects into table ${tableDefinitionNames.originalTableName} on schema ${schemaName}. Conflict on the specified columns will update values in the specified columns`,
{
insertObjects: rowsToUpsert,
conflictColumns: conflictColumns.map(col => tableDefinitionNames.originalColumnNames.get(col) ?? col).join(', '),
updateColumns: updateColumns.map(col => tableDefinitionNames.originalColumnNames.get(col) ?? col).join(', ')
}),

delete: async (object) => await this.dbOperationLog(blockHeight,
`Deleting objects which match the following object's values from table ${sanitizedTableName} on schema ${schemaName}`, object)
delete: async (whereObj) => await this.dbOperationLog(blockHeight,
`Deleting objects which match the following object's values from table ${tableDefinitionNames.originalTableName} on schema ${schemaName}`,
whereObj)
}
};

Expand All @@ -198,6 +209,7 @@ export default class IndexerRunner {
...funcForTable
};
}, {});
console.log(result);
return result;
} catch (error) {
console.warn('Caught error when generating context.db methods. Building no functions. You can still use other context object methods.\n', error);
Expand Down
75 changes: 52 additions & 23 deletions frontend/src/utils/pgSchemaTypeGen.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,78 @@ export class PgSchemaTypeGen {
this.tables = new Set();
}

sanitizeTableName(tableName) {
// Convert to PascalCase
let pascalCaseTableName = tableName
// Replace special characters with underscores
.replace(/[^a-zA-Z0-9_]/g, '_')
// Makes first letter and any letters following an underscore upper case
.replace(/^([a-zA-Z])|_([a-zA-Z])/g, (match) => match.toUpperCase())
// Removes all underscores
.replace(/_/g, '');
getColumnDefinitionNames (columnDefs) {
const columnDefinitionNames = new Map();
for (const columnDef of columnDefs) {
if (columnDef.column?.type === 'column_ref') {
const columnNameDef = columnDef.column.column.expr;
const actualColumnName = columnNameDef.type === 'double_quote_string' ? `"${columnNameDef.value}"` : columnNameDef.value;
columnDefinitionNames.set(columnNameDef.value, actualColumnName);
}
}
return columnDefinitionNames;
}

retainOriginalQuoting (schema, tableName) {
const createTableQuotedRegex = `\\b(create|CREATE)\\s+(table|TABLE)\\s+"${tableName}"\\s*`;

// Add underscore if first character is a number
if (/^[0-9]/.test(pascalCaseTableName)) {
pascalCaseTableName = '_' + pascalCaseTableName;
if (schema.match(new RegExp(createTableQuotedRegex, 'i'))) {
return `"${tableName}"`;
}

return pascalCaseTableName;
return tableName;
}

getTableNames(schema) {
getTableNameToDefinitionNamesMapping (schema) {
let schemaSyntaxTree = this.parser.astify(schema, { database: 'Postgresql' });
schemaSyntaxTree = Array.isArray(schemaSyntaxTree) ? schemaSyntaxTree : [schemaSyntaxTree]; // Ensure iterable
const tableNames = new Set();
const tableNameToDefinitionNamesMap = new Map();

// Collect all table names from schema AST, throw error if duplicate table names exist
for (const statement of schemaSyntaxTree) {
if (statement.type === 'create' && statement.keyword === 'table' && statement.table !== undefined) {
const tableName = statement.table[0].table;

if (tableNames.has(tableName)) {
if (tableNameToDefinitionNamesMap.has(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.`);
}

tableNames.add(tableName);
// Generate column lookup for table
const createDefs = statement.create_definitions ?? [];
for (const columnDef of createDefs) {
if (columnDef.column?.type === 'column_ref') {
const tableDefinitionNames = {
originalTableName: this.retainOriginalQuoting(schema, tableName),
originalColumnNames: this.getColumnDefinitionNames(createDefs)
};
tableNameToDefinitionNamesMap.set(tableName, tableDefinitionNames);
}
}
}
}

// Ensure schema is not empty
if (tableNames.size === 0) {
if (tableNameToDefinitionNamesMap.size === 0) {
throw new Error('Schema does not have any tables. There should be at least one table.');
}

const tableNamesArray = Array.from(tableNames);
return Array.from(tableNamesArray);
return tableNameToDefinitionNamesMap;
}

sanitizeTableName(tableName) {
// Convert to PascalCase
let pascalCaseTableName = tableName
// Replace special characters with underscores
.replace(/[^a-zA-Z0-9_]/g, '_')
// Makes first letter and any letters following an underscore upper case
.replace(/^([a-zA-Z])|_([a-zA-Z])/g, (match) => match.toUpperCase())
// Removes all underscores
.replace(/_/g, '');

// Add underscore if first character is a number
if (/^[0-9]/.test(pascalCaseTableName)) {
pascalCaseTableName = '_' + pascalCaseTableName;
}

return pascalCaseTableName;
}

generateTypes(sqlSchema) {
Expand Down

0 comments on commit bc1f339

Please sign in to comment.