Skip to content

Commit 85dd2b5

Browse files
authored
fix: schema generation joins and quotes (#6695)
1 parent ac2ec99 commit 85dd2b5

File tree

4 files changed

+77
-37
lines changed

4 files changed

+77
-37
lines changed

packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export class ScaffoldingSchema {
169169
const definition = {
170170
schema, table, tableDefinition, tableName
171171
};
172-
const tableizeName = inflection.tableize(table);
172+
const tableizeName = inflection.tableize(this.fixCase(table));
173173
const parts = tableizeName.split('_');
174174
const tableNamesFromParts = R.range(0, parts.length - 1).map(toDrop => inflection.tableize(R.drop(toDrop, parts).join('_')));
175175
const names = R.uniq([table, tableizeName].concat(tableNamesFromParts));
@@ -228,7 +228,7 @@ export class ScaffoldingSchema {
228228

229229
if (column.columnType !== 'time') {
230230
res.isPrimaryKey = column.attributes?.includes('primaryKey') ||
231-
column.name.toLowerCase() === 'id';
231+
this.fixCase(column.name) === 'id';
232232
}
233233
return res;
234234
});
@@ -238,7 +238,7 @@ export class ScaffoldingSchema {
238238
return tableDefinition.filter(
239239
column => (!column.name.startsWith('_') &&
240240
(this.columnType(column) === 'number') &&
241-
(this.options.includeNonDictionaryMeasures ? column.name.toLowerCase() !== 'id' : this.fromMeasureDictionary(column)))
241+
(this.options.includeNonDictionaryMeasures ? this.fixCase(column.name) !== 'id' : this.fromMeasureDictionary(column)))
242242
).map(column => ({
243243
name: column.name,
244244
types: ['sum', 'avg', 'min', 'max'],
@@ -248,14 +248,14 @@ export class ScaffoldingSchema {
248248
}
249249

250250
protected fromMeasureDictionary(column) {
251-
return !column.name.match(new RegExp(idRegex, 'i')) && !!MEASURE_DICTIONARY.find(word => column.name.toLowerCase().endsWith(word));
251+
return !column.name.match(new RegExp(idRegex, 'i')) && !!MEASURE_DICTIONARY.find(word => this.fixCase(column.name).endsWith(word));
252252
}
253253

254254
protected dimensionColumns(tableDefinition: any) {
255255
const dimensionColumns = tableDefinition.filter(
256256
column => !column.name.startsWith('_') && this.columnType(column) === 'string' ||
257257
column.attributes?.includes('primaryKey') ||
258-
column.name.toLowerCase() === 'id'
258+
this.fixCase(column.name) === 'id'
259259
);
260260

261261
const timeColumns = R.pipe(
@@ -269,15 +269,25 @@ export class ScaffoldingSchema {
269269

270270
return dimensionColumns.concat(timeColumns);
271271
}
272+
273+
private fixCase(value: string) {
274+
if (this.options.snakeCase) {
275+
return toSnakeCase(value);
276+
}
277+
278+
return value.toLocaleLowerCase();
279+
}
272280

273281
protected joins(tableName: TableName, tableDefinition) {
274282
return R.unnest(tableDefinition
275-
.filter(column => (column.name.match(new RegExp(idRegex, 'i')) && column.name.toLowerCase() !== 'id'))
283+
.filter(column => (column.name.match(new RegExp(idRegex, 'i')) && this.fixCase(column.name) !== 'id'))
276284
.map(column => {
277285
const withoutId = column.name.replace(new RegExp(idRegex, 'i'), '');
278286
const tablesToJoin = this.tableNamesToTables[withoutId] ||
279-
this.tableNamesToTables[inflection.tableize(withoutId)];
280-
287+
this.tableNamesToTables[inflection.tableize(withoutId)] ||
288+
this.tableNamesToTables[this.fixCase(withoutId)] ||
289+
this.tableNamesToTables[(inflection.tableize(this.fixCase(withoutId)))];
290+
281291
if (!tablesToJoin) {
282292
return null;
283293
}
@@ -286,8 +296,8 @@ export class ScaffoldingSchema {
286296
if (tableName === definition.tableName) {
287297
return null;
288298
}
289-
let columnForJoin = definition.tableDefinition.find(c => c.name.toLowerCase() === column.name.toLowerCase());
290-
columnForJoin = columnForJoin || definition.tableDefinition.find(c => c.name.toLowerCase() === 'id');
299+
let columnForJoin = definition.tableDefinition.find(c => this.fixCase(c.name) === this.fixCase(column.name));
300+
columnForJoin = columnForJoin || definition.tableDefinition.find(c => this.fixCase(c.name) === 'id');
291301
if (!columnForJoin) {
292302
return null;
293303
}
@@ -310,7 +320,7 @@ export class ScaffoldingSchema {
310320
}
311321

312322
protected timeColumnIndex(column): number {
313-
const name = column.name.toLowerCase();
323+
const name = this.fixCase(column.name);
314324
if (name.indexOf('create') !== -1) {
315325
return 0;
316326
} else if (name.indexOf('update') !== -1) {
@@ -321,7 +331,7 @@ export class ScaffoldingSchema {
321331
}
322332

323333
protected columnType(column): ColumnType {
324-
const type = column.type.toLowerCase();
334+
const type = this.fixCase(column.type);
325335
if (['time', 'date'].find(t => type.indexOf(t) !== -1)) {
326336
return ColumnType.Time;
327337
} else if (['int', 'dec', 'double', 'numb'].find(t => type.indexOf(t) !== -1)) {

packages/cubejs-schema-compiler/src/scaffolding/formatters/BaseSchemaFormatter.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,21 @@ export abstract class BaseSchemaFormatter {
9999
}
100100

101101
protected eligibleIdentifier(name: string) {
102-
return !!name.match(/^[a-z0-9_]+$/);
102+
return !!name.match(/^[a-z0-9_]+$/i);
103103
}
104104

105105
public schemaDescriptorForTable(tableSchema: TableSchema, schemaContext: SchemaContext = {}) {
106106
const table = `${
107107
tableSchema.schema?.length ? `${this.escapeName(tableSchema.schema)}.` : ''
108108
}${this.escapeName(tableSchema.table)}`;
109-
109+
110110
const { dataSource, ...contextProps } = schemaContext;
111-
111+
112112
let dataSourceProp = {};
113113
if (dataSource) {
114114
dataSourceProp = this.options.snakeCase ? { data_source: dataSource } : { dataSource };
115115
}
116-
116+
117117
const sqlOption = this.options.snakeCase
118118
? {
119119
sql_table: table,
@@ -126,7 +126,7 @@ export abstract class BaseSchemaFormatter {
126126
cube: tableSchema.cube,
127127
...sqlOption,
128128
...dataSourceProp,
129-
129+
130130
joins: tableSchema.joins
131131
.map((j) => ({
132132
[j.cubeToJoin]: {
@@ -164,7 +164,7 @@ export abstract class BaseSchemaFormatter {
164164
type: 'count',
165165
},
166166
}),
167-
167+
168168
...(this.options.snakeCase
169169
? Object.fromEntries(
170170
Object.entries(contextProps).map(([key, value]) => [toSnakeCase(key), value])
Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,40 @@
1-
export function toSnakeCase(str: string) {
2-
const match = str.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g);
1+
const PUNCTUATION = /[^\p{L}\p{N}]+/ug;
2+
const REGEX = /([\p{Lu}]+[\p{Ll}\p{N}]*|[\p{Ll}\p{N}]+)/gu;
3+
const LAZY_UPPERCASE_REGEX = /([\p{Lu}]{2,}(?![\p{Ll}\p{N}])|[\p{Lu}]+[\p{Ll}\p{N}]*|[\p{Ll}\p{N}]+)/gu;
4+
const PRESERVE_UPPERCASE_REGEX = /([\p{Lu}]{2,}|[\p{Lu}][\p{Ll}]*|[\p{Ll}\p{N}]+)/gu;
35

4-
if (!match) {
5-
return str;
6+
const splitString = (value: string, options: any = {}) => {
7+
// eslint-disable-next-line no-nested-ternary
8+
const regex = options.preserveConsecutiveUppercase
9+
? PRESERVE_UPPERCASE_REGEX
10+
: (options.lazyUppercase !== false ? LAZY_UPPERCASE_REGEX : REGEX);
11+
12+
const input = value.trim();
13+
const words = value ? (input.match(regex) || []).filter(Boolean) : [];
14+
const output = words.filter(Boolean);
15+
16+
if (output.length === 0 && value.length > 0) {
17+
return [value.replace(PUNCTUATION, '')];
18+
}
19+
20+
return output;
21+
};
22+
23+
const transformWords = (input: string, options, joinChar = '', transformFn = s => s) => (input ? splitString(input, options).map(transformFn).join(joinChar) : '');
24+
25+
const lowercase = (input = '', options) => input.toLocaleLowerCase(options?.locale);
26+
27+
type Options = {
28+
uppercase?: boolean;
29+
locale?: string;
30+
};
31+
32+
export function toSnakeCase(input = '', options: Options = {}) {
33+
const output = lowercase(transformWords(input, options, '_'), options);
34+
35+
if (options?.uppercase) {
36+
return output.toLocaleUpperCase(options?.locale);
637
}
738

8-
return match.map(v => v.toLowerCase())
9-
.join('_');
39+
return output;
1040
}

packages/cubejs-schema-compiler/test/unit/scaffolding-template.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('ScaffoldingTemplate', () => {
110110
111111
joins: {
112112
Customers: {
113-
sql: \`\${CUBE}."customerId" = \${Customers}.id\`,
113+
sql: \`\${CUBE}.customerId = \${Customers}.id\`,
114114
relationship: \`belongsTo\`
115115
}
116116
},
@@ -148,7 +148,7 @@ describe('ScaffoldingTemplate', () => {
148148
149149
joins: {
150150
Accounts: {
151-
sql: \`\${CUBE}."accountId" = \${Accounts}.id\`,
151+
sql: \`\${CUBE}.accountId = \${Accounts}.id\`,
152152
relationship: \`belongsTo\`
153153
}
154154
},
@@ -217,7 +217,7 @@ describe('ScaffoldingTemplate', () => {
217217
},
218218
219219
failurecount: {
220-
sql: \`\${CUBE}."failureCount"\`,
220+
sql: \`failureCount\`,
221221
type: \`sum\`
222222
}
223223
},
@@ -251,7 +251,7 @@ describe('ScaffoldingTemplate', () => {
251251
252252
joins: {
253253
customers: {
254-
sql: \`\${CUBE}."customerId" = \${customers}.id\`,
254+
sql: \`\${CUBE}.customerId = \${customers}.id\`,
255255
relationship: \`many_to_one\`
256256
}
257257
},
@@ -289,7 +289,7 @@ describe('ScaffoldingTemplate', () => {
289289
290290
joins: {
291291
accounts: {
292-
sql: \`\${CUBE}."accountId" = \${accounts}.id\`,
292+
sql: \`\${CUBE}.accountId = \${accounts}.id\`,
293293
relationship: \`many_to_one\`
294294
}
295295
},
@@ -358,7 +358,7 @@ describe('ScaffoldingTemplate', () => {
358358
},
359359
360360
failurecount: {
361-
sql: \`\${CUBE}."failureCount"\`,
361+
sql: \`failureCount\`,
362362
type: \`sum\`
363363
}
364364
},
@@ -407,7 +407,7 @@ describe('ScaffoldingTemplate', () => {
407407
{
408408
fileName: 'some_orders.js',
409409
content: `cube(\`some_orders\`, {
410-
sql_table: \`public.\\\`someOrders\\\`\`,
410+
sql_table: \`public.someOrders\`,
411411
412412
joins: {
413413
@@ -421,7 +421,7 @@ describe('ScaffoldingTemplate', () => {
421421
},
422422
423423
somedimension: {
424-
sql: \`\${CUBE}.\\\`someDimension\\\`\`,
424+
sql: \`someDimension\`,
425425
type: \`string\`
426426
}
427427
},
@@ -592,7 +592,7 @@ describe('ScaffoldingTemplate', () => {
592592
format: SchemaFormat.Yaml,
593593
snakeCase: true
594594
});
595-
595+
596596
expect(
597597
template.generateFilesByTableNames([
598598
'public.orders',
@@ -608,7 +608,7 @@ describe('ScaffoldingTemplate', () => {
608608
609609
joins:
610610
- name: customers
611-
sql: "{CUBE}.\\"customerId\\" = {customers}.id"
611+
sql: "{CUBE}.customerId = {customers}.id"
612612
relationship: many_to_one
613613
614614
dimensions:
@@ -639,7 +639,7 @@ describe('ScaffoldingTemplate', () => {
639639
640640
joins:
641641
- name: accounts
642-
sql: "{CUBE}.\\"accountId\\" = {accounts}.id"
642+
sql: "{CUBE}.accountId = {accounts}.id"
643643
relationship: many_to_one
644644
645645
dimensions:
@@ -693,7 +693,7 @@ describe('ScaffoldingTemplate', () => {
693693
type: count
694694
695695
- name: failurecount
696-
sql: "{CUBE}.\\"failureCount\\""
696+
sql: failureCount
697697
type: sum
698698
699699
pre_aggregations:
@@ -747,7 +747,7 @@ describe('ScaffoldingTemplate', () => {
747747
type: count
748748
749749
- name: failurecount
750-
sql: "{CUBE}.\`failureCount\`"
750+
sql: failureCount
751751
type: sum
752752
753753
pre_aggregations:

0 commit comments

Comments
 (0)