Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FAI-4462: Move hasura schema dependencies to faros-js-client repo #42

Merged
merged 3 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/graphql/hasura-schema-loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ok} from 'assert';
import axios, {AxiosInstance} from 'axios';
import fs from 'fs-extra';
import {camelCase, find} from 'lodash';
import {camelCase, find, snakeCase} from 'lodash';
import path from 'path';
import toposort from 'toposort';
import {Dictionary} from 'ts-essentials';
Expand All @@ -11,11 +11,14 @@ import {
foreignKeyForArray,
foreignKeyForObj,
isManualConfiguration,
MULTI_TENANT_COLUMNS,
parsePrimaryKeys,
SchemaLoader,
} from './schema';
import {
ArrayForeignKey,
ArrayRelationship,
BackReference,
ObjectRelationship,
Reference,
Schema,
Expand Down Expand Up @@ -75,13 +78,7 @@ export class HasuraSchemaLoader implements SchemaLoader {
result
.filter((row) => row[0] !== 'table_name')
.forEach(([table, exp]) => {
// TODO: better way to do this?
primaryKeys[table] = exp
.replace('pkey(VARIADIC ARRAY[', '')
.replace('])', '')
.split(', ')
.map((col) => col.replace(/"/g, ''))
.map((col) => (this.camelCaseFieldNames ? camelCase(col) : col));
primaryKeys[table] = parsePrimaryKeys(exp, this.camelCaseFieldNames);
});
return primaryKeys;
}
Expand All @@ -95,7 +92,7 @@ export class HasuraSchemaLoader implements SchemaLoader {
* targetTable (e.g. cicd_Pipeline): table.table.name
*
* The output, res, can be used as:
* res['cicd_Build']['pipeline_id'] => 'cicd_Pipeline
* res['cicd_Build']['pipeline_id'] => 'cicd_Pipeline'
*/
static indexFkTargetModels(source: Source): Dictionary<Dictionary<string>> {
const res: Dictionary<Dictionary<string>> = {};
Expand Down Expand Up @@ -129,7 +126,7 @@ export class HasuraSchemaLoader implements SchemaLoader {
const tableNames = [];
const scalars: Dictionary<Dictionary<string>> = {};
const references: Dictionary<Dictionary<Reference>> = {};
const backReferences: Dictionary<Reference[]> = {};
const backReferences: Dictionary<BackReference[]> = {};
for (const table of source.tables) {
const tableName = table.table.name;
tableNames.push(tableName);
Expand All @@ -148,8 +145,10 @@ export class HasuraSchemaLoader implements SchemaLoader {
);
const tableScalars: Dictionary<string> = {};
for (const scalar of scalarTypes) {
tableScalars[scalar.name] =
scalar.type.ofType?.name ?? scalar.type.name;
if (!MULTI_TENANT_COLUMNS.has(snakeCase(scalar.name))) {
tableScalars[scalar.name] =
scalar.type.ofType?.name ?? scalar.type.name;
}
}
scalars[tableName] = tableScalars;
const tableReferences: Dictionary<Reference> = {};
Expand All @@ -159,6 +158,7 @@ export class HasuraSchemaLoader implements SchemaLoader {
const relMetadata = {
field: rel.name,
model: targetTableByFk[table.table.name][fk],
foreignKey: relFldName
};
// index relation metadata using both FK column and rel.name
// this is needed for cross-compatibility with CE and SaaS
Expand Down
30 changes: 29 additions & 1 deletion src/graphql/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ok} from 'assert';
import _ from 'lodash';
import _, {camelCase} from 'lodash';

import {
ArrayForeignKey,
Expand All @@ -10,6 +10,9 @@ import {
Schema,
} from './types';

export const MULTI_TENANT_COLUMNS = new Set(['tenant_id', 'graph_name']);
const PG_TYPE_REGEX = /\((.*)\)::.*/;

export function foreignKeyForObj(rel: ObjectRelationship): string {
if (isManualConfiguration(rel.using)) {
// for object rel, column_mapping contains one entry
Expand Down Expand Up @@ -46,6 +49,31 @@ export function foreignKeyForArray(rel: ArrayRelationship): string {
return fk;
}

/**
* Parse elements of primary key from pkey function definition.
* e.g. pkey(VARIADIC ARRAY[tenant_id, graph_name, source, uid])
*/
export function parsePrimaryKeys(
exp: string,
camelCaseFieldNames: boolean,
includeMultiTenantColumns = false
): string [] {
return exp
.replace('pkey(VARIADIC ARRAY[', '')
.replace('])', '')
.split(', ')
.map((col) => col.replace(/"/g, ''))
.map((col) => {
// extract col from types e.g. foo::text => foo
const matches = col.match(PG_TYPE_REGEX);
return matches ? matches[1] : col;
})
.filter((col) =>
// conditionally filter multi-tenant columns
includeMultiTenantColumns || !MULTI_TENANT_COLUMNS.has(col))
.map((col) => (camelCaseFieldNames ? camelCase(col) : col));
}

export function isManualConfiguration(
using: ManualConfiguration | any
): using is ManualConfiguration {
Expand Down
8 changes: 6 additions & 2 deletions src/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,20 @@ export interface ArrayRelationship {
using: ArrayForeignKey | ManualConfiguration;
}

export interface Reference {
export interface BackReference {
field: string;
model: string;
}

export interface Reference extends BackReference {
foreignKey: string;
}

export interface Schema {
primaryKeys: Dictionary<string[]>;
scalars: Dictionary<Dictionary<string>>;
references: Dictionary<Dictionary<Reference>>;
backReferences: Dictionary<Reference[]>;
backReferences: Dictionary<BackReference[]>;
sortedModelDependencies: ReadonlyArray<string>;
tableNames: ReadonlyArray<string>;
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export {
ObjectForeignKey,
ObjectRelationship,
Reference,
BackReference,
Schema,
PathToModel,
Query,
Expand Down
1 change: 1 addition & 0 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ describe('client', () => {
owner: {
field: 'cal_User',
model: 'cal_User',
foreignKey: 'user'
},
},
},
Expand Down
40 changes: 40 additions & 0 deletions test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {parsePrimaryKeys} from '../src/graphql/schema';
import * as sut from '../src/utils';

describe('utils', () => {
Expand All @@ -10,3 +11,42 @@ describe('utils', () => {
expect(f('https://example.com///')).toEqual('https://example.com');
});
});

describe('parse primary keys', () => {
test('multi-tenant columns', () => {
expect(parsePrimaryKeys(
'pkey(VARIADIC ARRAY[tenant_id, graph_name, source, uid])',
true,
false)
).toEqual(['source', 'uid']);
expect(parsePrimaryKeys(
'pkey(VARIADIC ARRAY[tenant_id, graph_name, source, uid])',
true,
true)
).toEqual(['tenantId', 'graphName', 'source', 'uid']);
});
test('camel case', () => {
expect(parsePrimaryKeys(
'pkey(VARIADIC ARRAY[tenant_id, graph_name, source, uid])',
false,
true)
).toEqual(['tenant_id', 'graph_name', 'source', 'uid']);
});
test('remove PG type info', () => {
expect(parsePrimaryKeys(
// eslint-disable-next-line max-len
'pkey(VARIADIC ARRAY[tenant_id, graph_name, (number)::text, repository_id])',
true)
).toEqual(['number', 'repositoryId']);
expect(parsePrimaryKeys(
// eslint-disable-next-line max-len
'pkey(VARIADIC ARRAY[tenant_id, graph_name, (number)::text, repository_id])',
false)
).toEqual(['number', 'repository_id']);
expect(parsePrimaryKeys(
// eslint-disable-next-line max-len
'pkey(VARIADIC ARRAY[tenant_id, graph_name, (lat)::text, (lon)::text])',
true)
).toEqual(['lat', 'lon']);
});
});