Skip to content

Commit 5995f4e

Browse files
author
Elias Kassell
authored
Fix publish type backwards compatibility (#1910)
* Progress fixing type backwards compatibility * Progress to class extension error * Move reset table site def back to callsites * Fix class inheritence error * Fix various type conflict issues * Fix type parameter conversion * Fix lint * Bump version
1 parent 167f36a commit 5995f4e

File tree

9 files changed

+467
-242
lines changed

9 files changed

+467
-242
lines changed

core/actions/incremental_table.ts

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { verifyObjectMatchesProto, VerifyProtoErrorBehaviour } from "df/common/protos";
22
import {
33
ActionBuilder,
4-
ILegacyTableBigqueryConfig,
4+
ILegacyBigQueryOptions,
5+
ILegacyTableConfig,
56
ITableContext,
6-
LegacyConfigConverter
7+
LegacyConfigConverter,
8+
TableType
79
} from "df/core/actions";
810
import { Assertion } from "df/core/actions/assertion";
11+
import { Table } from "df/core/actions/table";
12+
import { View } from "df/core/actions/view";
913
import { ColumnDescriptors } from "df/core/column_descriptors";
1014
import { Contextable, Resolvable } from "df/core/common";
1115
import * as Path from "df/core/path";
@@ -25,25 +29,6 @@ import {
2529
} from "df/core/utils";
2630
import { dataform } from "df/protos/ts";
2731

28-
/**
29-
* @hidden
30-
* This maintains backwards compatability with older versions.
31-
* TODO(ekrekr): consider breaking backwards compatability of these in v4.
32-
*/
33-
export interface ILegacyIncrementalTableConfig
34-
extends dataform.ActionConfig.IncrementalTableConfig {
35-
dependencies: Resolvable[];
36-
database: string;
37-
schema: string;
38-
fileName: string;
39-
type: string;
40-
bigquery?: ILegacyTableBigqueryConfig;
41-
// Legacy incremental table config's table assertions cannot directly extend the protobuf
42-
// incremental table config definition because of legacy incremental table config's flexible
43-
// types.
44-
assertions: any;
45-
}
46-
4732
/**
4833
* @hidden
4934
*/
@@ -71,9 +56,15 @@ export class IncrementalTable extends ActionBuilder<dataform.Table> {
7156
private uniqueKeyAssertions: Assertion[] = [];
7257
private rowConditionsAssertion: Assertion;
7358

59+
private unverifiedConfig: any;
60+
private configPath: string | undefined;
61+
7462
constructor(session?: Session, unverifiedConfig?: any, configPath?: string) {
7563
super(session);
7664
this.session = session;
65+
this.configPath = configPath;
66+
// A copy is used here to prevent manipulation of the original.
67+
this.unverifiedConfig = Object.assign({}, unverifiedConfig);
7768

7869
if (!unverifiedConfig) {
7970
return;
@@ -158,11 +149,48 @@ export class IncrementalTable extends ActionBuilder<dataform.Table> {
158149
if (config.filename) {
159150
this.proto.fileName = config.filename;
160151
}
161-
this.proto.onSchemaChange = this.mapOnSchemaChange(config.onSchemaChange)
152+
this.proto.onSchemaChange = this.mapOnSchemaChange(config.onSchemaChange);
162153

163154
return this;
164155
}
165156

157+
/**
158+
* @hidden
159+
* @deprecated
160+
* Deprecated in favor of action type can being set in the configs passed to action constructor
161+
* functions.
162+
*/
163+
public type(type: TableType) {
164+
let newAction: View | Table;
165+
switch (type) {
166+
case "table":
167+
newAction = new Table(
168+
this.session,
169+
{ ...this.unverifiedConfig, type: "table" },
170+
this.configPath
171+
);
172+
break;
173+
case "incremental":
174+
return this;
175+
case "view":
176+
newAction = new View(
177+
this.session,
178+
{ ...this.unverifiedConfig, type: "view" },
179+
this.configPath
180+
);
181+
break;
182+
default:
183+
throw new Error(`Unexpected table type: ${type}`);
184+
}
185+
const existingAction = this.session.actions.indexOf(this);
186+
if (existingAction === -1) {
187+
throw Error(
188+
"Expected pre-existing action, but none found. Please report this to the Dataform team."
189+
);
190+
}
191+
this.session.actions[existingAction] = newAction;
192+
}
193+
166194
public query(query: Contextable<ITableContext, string>) {
167195
this.contextableQuery = query;
168196
return this;
@@ -398,8 +426,15 @@ export class IncrementalTable extends ActionBuilder<dataform.Table> {
398426
return protoOps;
399427
}
400428

429+
/**
430+
* Verify config checks that the constructor provided config matches the expected proto structure,
431+
* or the previously accepted legacy structure. If the legacy structure is used, it is converted
432+
* to the new structure.
433+
*/
401434
private verifyConfig(
402-
unverifiedConfig: ILegacyIncrementalTableConfig
435+
// `any` is used here to facilitate the type merging of the legacy table config, which is very
436+
// different to the new structure.
437+
unverifiedConfig: dataform.ActionConfig.IncrementalTableConfig | ILegacyTableConfig | any
403438
): dataform.ActionConfig.IncrementalTableConfig {
404439
// The "type" field only exists on legacy incremental table configs. Here we convert them to the
405440
// new format.
@@ -441,7 +476,7 @@ export class IncrementalTable extends ActionBuilder<dataform.Table> {
441476
throw e;
442477
},
443478
unverifiedConfig.bigquery,
444-
strictKeysOf<ILegacyTableBigqueryConfig>()([
479+
strictKeysOf<ILegacyBigQueryOptions>()([
445480
"partitionBy",
446481
"clusterBy",
447482
"updatePartitionFilter",
@@ -471,27 +506,37 @@ export class IncrementalTable extends ActionBuilder<dataform.Table> {
471506
// - for sqlx it will have type "string"
472507
// - for action.yaml it will be converted to enum which is represented
473508
// in TypeScript as a "number".
474-
private mapOnSchemaChange(onSchemaChange?: string|number) : dataform.OnSchemaChange {
509+
private mapOnSchemaChange(onSchemaChange?: string | number): dataform.OnSchemaChange {
475510
if (!onSchemaChange) {
476511
return dataform.OnSchemaChange.IGNORE;
477512
}
478513

479514
if (typeof onSchemaChange === "number") {
480515
switch (onSchemaChange) {
481-
case dataform.ActionConfig.OnSchemaChange.IGNORE: return dataform.OnSchemaChange.IGNORE;
482-
case dataform.ActionConfig.OnSchemaChange.FAIL: return dataform.OnSchemaChange.FAIL;
483-
case dataform.ActionConfig.OnSchemaChange.EXTEND: return dataform.OnSchemaChange.EXTEND;
484-
case dataform.ActionConfig.OnSchemaChange.SYNCHRONIZE: return dataform.OnSchemaChange.SYNCHRONIZE;
485-
default: throw new Error(`OnSchemaChange value "${onSchemaChange}" is not supported`);
516+
case dataform.ActionConfig.OnSchemaChange.IGNORE:
517+
return dataform.OnSchemaChange.IGNORE;
518+
case dataform.ActionConfig.OnSchemaChange.FAIL:
519+
return dataform.OnSchemaChange.FAIL;
520+
case dataform.ActionConfig.OnSchemaChange.EXTEND:
521+
return dataform.OnSchemaChange.EXTEND;
522+
case dataform.ActionConfig.OnSchemaChange.SYNCHRONIZE:
523+
return dataform.OnSchemaChange.SYNCHRONIZE;
524+
default:
525+
throw new Error(`OnSchemaChange value "${onSchemaChange}" is not supported`);
486526
}
487527
}
488528

489529
switch (onSchemaChange.toString().toUpperCase()) {
490-
case "IGNORE": return dataform.OnSchemaChange.IGNORE;
491-
case "FAIL": return dataform.OnSchemaChange.FAIL;
492-
case "EXTEND": return dataform.OnSchemaChange.EXTEND;
493-
case "SYNCHRONIZE": return dataform.OnSchemaChange.SYNCHRONIZE;
494-
default: throw new Error(`OnSchemaChange value "${onSchemaChange}" is not supported`);
530+
case "IGNORE":
531+
return dataform.OnSchemaChange.IGNORE;
532+
case "FAIL":
533+
return dataform.OnSchemaChange.FAIL;
534+
case "EXTEND":
535+
return dataform.OnSchemaChange.EXTEND;
536+
case "SYNCHRONIZE":
537+
return dataform.OnSchemaChange.SYNCHRONIZE;
538+
default:
539+
throw new Error(`OnSchemaChange value "${onSchemaChange}" is not supported`);
495540
}
496541
}
497542
}

core/actions/index.ts

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { Assertion } from "df/core/actions/assertion";
22
import { DataPreparation } from "df/core/actions/data_preparation";
33
import { Declaration } from "df/core/actions/declaration";
4-
import { ILegacyIncrementalTableConfig, IncrementalTable } from "df/core/actions/incremental_table";
4+
import { IncrementalTable } from "df/core/actions/incremental_table";
55
import { Notebook } from "df/core/actions/notebook";
66
import { Operation } from "df/core/actions/operation";
7-
import { ILegacyTableConfig, Table } from "df/core/actions/table";
8-
import { ILegacyViewConfig, View } from "df/core/actions/view";
9-
import { ICommonContext } from "df/core/common";
7+
import { Table } from "df/core/actions/table";
8+
import { View } from "df/core/actions/view";
9+
import {
10+
IActionConfig,
11+
ICommonContext,
12+
IDependenciesConfig,
13+
IDocumentableConfig,
14+
INamedConfig,
15+
ITargetableConfig
16+
} from "df/core/common";
1017
import { Session } from "df/core/session";
1118
import { dataform } from "df/protos/ts";
1219

@@ -114,6 +121,71 @@ export interface ITableContext extends ICommonContext {
114121
incremental: () => boolean;
115122
}
116123

124+
/**
125+
* @hidden
126+
* @deprecated
127+
* This is no longer needed other than for legacy backwards compatibility purposes, as tables are
128+
* now configured in separate actions.
129+
*/
130+
export type TableType = typeof TableType[number];
131+
132+
/**
133+
* @hidden
134+
* @deprecated
135+
* This is no longer needed other than for legacy backwards compatibility purposes, as tables are
136+
* now configured in separate actions.
137+
*/
138+
export const TableType = ["table", "view", "incremental"] as const;
139+
140+
/**
141+
* @hidden
142+
* @deprecated
143+
* These options are only here to preserve backwards compatibility of legacy config options.
144+
* consider breaking backwards compatability of this in v4.
145+
*/
146+
export interface ILegacyTableConfig
147+
extends IActionConfig,
148+
IDependenciesConfig,
149+
IDocumentableConfig,
150+
INamedConfig,
151+
ITargetableConfig {
152+
type?: TableType;
153+
protected?: boolean;
154+
bigquery?: ILegacyBigQueryOptions;
155+
assertions?: ILegacyTableAssertions;
156+
uniqueKey?: string[];
157+
materialized?: boolean;
158+
}
159+
160+
/**
161+
* @hidden
162+
* @deprecated
163+
* These options are only here to preserve backwards compatibility of legacy config options.
164+
* consider breaking backwards compatability of this in v4.
165+
*/
166+
export interface ILegacyBigQueryOptions {
167+
partitionBy?: string;
168+
clusterBy?: string[];
169+
updatePartitionFilter?: string;
170+
labels?: { [name: string]: string };
171+
partitionExpirationDays?: number;
172+
requirePartitionFilter?: boolean;
173+
additionalOptions?: { [name: string]: string };
174+
}
175+
176+
/**
177+
* @hidden
178+
* @deprecated
179+
* These options are only here to preserve backwards compatibility of legacy config options.
180+
* consider breaking backwards compatability of this in v4.
181+
*/
182+
export interface ILegacyTableAssertions {
183+
uniqueKey?: string | string[];
184+
uniqueKeys?: string[][];
185+
nonNull?: string | string[];
186+
rowConditions?: string[];
187+
}
188+
117189
export class LegacyConfigConverter {
118190
// This is a workaround to make bigquery options output empty fields with the same behaviour as
119191
// they did previously.
@@ -137,9 +209,11 @@ export class LegacyConfigConverter {
137209
return bigqueryFiltered;
138210
}
139211

140-
public static insertLegacyInlineAssertionsToConfigProto<
141-
T extends ILegacyTableConfig | ILegacyIncrementalTableConfig | ILegacyViewConfig
142-
>(legacyConfig: T): T {
212+
public static insertLegacyInlineAssertionsToConfigProto<T extends ILegacyTableConfig>(
213+
unverifiedConfig: T
214+
): T {
215+
// Type `any` is used here to facilitate the type hacking for legacy compatibility.
216+
const legacyConfig: any = unverifiedConfig;
143217
if (legacyConfig?.assertions) {
144218
if (typeof legacyConfig.assertions?.uniqueKey === "string") {
145219
legacyConfig.assertions.uniqueKey = [legacyConfig.assertions.uniqueKey];
@@ -158,9 +232,11 @@ export class LegacyConfigConverter {
158232
return legacyConfig;
159233
}
160234

161-
public static insertLegacyBigQueryOptionsToConfigProto<
162-
T extends ILegacyTableConfig | ILegacyIncrementalTableConfig
163-
>(legacyConfig: T): T {
235+
public static insertLegacyBigQueryOptionsToConfigProto<T extends ILegacyTableConfig>(
236+
unverifiedConfig: T
237+
): T {
238+
// Type `any` is used here to facilitate the type hacking for legacy compatibility.
239+
const legacyConfig: any = unverifiedConfig;
164240
if (!legacyConfig?.bigquery) {
165241
return legacyConfig;
166242
}
@@ -173,8 +249,7 @@ export class LegacyConfigConverter {
173249
delete legacyConfig.bigquery.clusterBy;
174250
}
175251
if (!!legacyConfig.bigquery.updatePartitionFilter) {
176-
(legacyConfig as ILegacyIncrementalTableConfig).updatePartitionFilter =
177-
legacyConfig.bigquery.updatePartitionFilter;
252+
legacyConfig.updatePartitionFilter = legacyConfig.bigquery.updatePartitionFilter;
178253
delete legacyConfig.bigquery.updatePartitionFilter;
179254
}
180255
if (!!legacyConfig.bigquery.labels) {
@@ -201,13 +276,3 @@ export class LegacyConfigConverter {
201276
return legacyConfig;
202277
}
203278
}
204-
205-
export interface ILegacyTableBigqueryConfig {
206-
partitionBy?: string;
207-
clusterBy?: string[];
208-
updatePartitionFilter?: string;
209-
labels?: { [key: string]: string };
210-
partitionExpirationDays?: number;
211-
requirePartitionFilter?: boolean;
212-
additionalOptions?: { [key: string]: string };
213-
}

0 commit comments

Comments
 (0)