@ingestron/pack-databricks 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +108 -2
  4. package/dist/apply-contract.d.ts +17 -0
  5. package/dist/apply-contract.d.ts.map +1 -0
  6. package/dist/apply-contract.js +100 -0
  7. package/dist/apply-contract.js.map +1 -0
  8. package/dist/build.d.ts +8 -0
  9. package/dist/build.d.ts.map +1 -0
  10. package/dist/build.js +79 -0
  11. package/dist/build.js.map +1 -0
  12. package/dist/bundle.d.ts +31 -0
  13. package/dist/bundle.d.ts.map +1 -0
  14. package/dist/bundle.js +137 -0
  15. package/dist/bundle.js.map +1 -0
  16. package/dist/capabilities.d.ts +8 -0
  17. package/dist/capabilities.d.ts.map +1 -0
  18. package/dist/capabilities.js +78 -0
  19. package/dist/capabilities.js.map +1 -0
  20. package/dist/connect-method.d.ts +8 -0
  21. package/dist/connect-method.d.ts.map +1 -0
  22. package/dist/connect-method.js +89 -0
  23. package/dist/connect-method.js.map +1 -0
  24. package/dist/connect.d.ts +28 -0
  25. package/dist/connect.d.ts.map +1 -0
  26. package/dist/connect.js +153 -0
  27. package/dist/connect.js.map +1 -0
  28. package/dist/connectors.d.ts +4 -0
  29. package/dist/connectors.d.ts.map +1 -0
  30. package/dist/connectors.js +56 -0
  31. package/dist/connectors.js.map +1 -0
  32. package/dist/contract.d.ts +4 -0
  33. package/dist/contract.d.ts.map +1 -0
  34. package/dist/contract.js +54 -0
  35. package/dist/contract.js.map +1 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +3 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/methods.d.ts +10 -0
  41. package/dist/methods.d.ts.map +1 -0
  42. package/dist/methods.js +167 -0
  43. package/dist/methods.js.map +1 -0
  44. package/dist/pack.d.ts +9 -0
  45. package/dist/pack.d.ts.map +1 -0
  46. package/dist/pack.js +73 -0
  47. package/dist/pack.js.map +1 -0
  48. package/dist/pyspark-method.d.ts +4 -0
  49. package/dist/pyspark-method.d.ts.map +1 -0
  50. package/dist/pyspark-method.js +41 -0
  51. package/dist/pyspark-method.js.map +1 -0
  52. package/dist/python-data.d.ts +22 -0
  53. package/dist/python-data.d.ts.map +1 -0
  54. package/dist/python-data.js +50 -0
  55. package/dist/python-data.js.map +1 -0
  56. package/dist/query.d.ts +9 -0
  57. package/dist/query.d.ts.map +1 -0
  58. package/dist/query.js +26 -0
  59. package/dist/query.js.map +1 -0
  60. package/dist/request.d.ts +4 -0
  61. package/dist/request.d.ts.map +1 -0
  62. package/dist/request.js +47 -0
  63. package/dist/request.js.map +1 -0
  64. package/dist/sql-data.d.ts +45 -0
  65. package/dist/sql-data.d.ts.map +1 -0
  66. package/dist/sql-data.js +111 -0
  67. package/dist/sql-data.js.map +1 -0
  68. package/dist/sql.d.ts +22 -0
  69. package/dist/sql.d.ts.map +1 -0
  70. package/dist/sql.js +81 -0
  71. package/dist/sql.js.map +1 -0
  72. package/package.json +68 -3
  73. package/templates/apply-contract.py.liquid +58 -0
  74. package/templates/change-data.sql.liquid +20 -0
  75. package/templates/materialised-view.py.liquid +28 -0
  76. package/templates/materialised-view.sql.liquid +4 -0
  77. package/templates/read-files.liquid +7 -0
  78. package/templates/streaming-table.sql.liquid +3 -0
  79. package/templates/table.liquid +17 -0
@@ -0,0 +1,45 @@
1
+ import { type ResolvedTable } from "@ingestron/definitions";
2
+ /** The table definition shared by every SQL file. */
3
+ export interface TableData {
4
+ kind: "STREAMING TABLE" | "MATERIALIZED VIEW";
5
+ target: string;
6
+ constraints: {
7
+ name: string;
8
+ check: string;
9
+ on_violation: string;
10
+ }[];
11
+ comment: string;
12
+ cluster: string;
13
+ properties: {
14
+ key: string;
15
+ value: string;
16
+ }[];
17
+ }
18
+ /** A `read_files` call. */
19
+ export interface SourceData {
20
+ stream: boolean;
21
+ /** The select list: `*`, or with source columns renamed to their contract names. */
22
+ select: string;
23
+ path: string;
24
+ options: {
25
+ key: string;
26
+ value: string;
27
+ }[];
28
+ }
29
+ /** A SQL string literal. */
30
+ export declare function sqlString(text: string): string;
31
+ /** The definition of the table itself. */
32
+ export declare function tableData(table: ResolvedTable, kind: TableData["kind"]): TableData;
33
+ /** The `read_files` call for a flow table's files. */
34
+ export declare function sourceData(table: ResolvedTable, stream: boolean): SourceData;
35
+ /** The AUTO CDC parts of a change-data table. */
36
+ export declare function changeData(table: ResolvedTable): {
37
+ changes: string;
38
+ flow: string;
39
+ keys: string;
40
+ sequence: string;
41
+ delete_when: string;
42
+ except_columns: string;
43
+ scd: number;
44
+ };
45
+ //# sourceMappingURL=sql-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-data.d.ts","sourceRoot":"","sources":["../src/sql-data.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG1E,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,CAAC;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC9C;AAED,2BAA2B;AAC3B,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAID,4BAA4B;AAC5B,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,SAAS,CAgBlF;AASD,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,GAAG,UAAU,CAO5E;AA0DD,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa;;;;;;;;EAgB9C"}
@@ -0,0 +1,111 @@
1
+ // Template data for a table's SQL file. All quoting happens here, so the
2
+ // templates only arrange text. Key names avoid Liquid keywords (such as
3
+ // `required` or `with`) because python-liquid refuses them after a dot.
4
+ import { fullNameText } from "@ingestron/definitions";
5
+ import { quoteName } from "./query.js";
6
+ const onViolation = { warn: "", drop: " ON VIOLATION DROP ROW", fail: " ON VIOLATION FAIL UPDATE" };
7
+ /** A SQL string literal. */
8
+ export function sqlString(text) {
9
+ return `'${text.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
10
+ }
11
+ /** The definition of the table itself. */
12
+ export function tableData(table, kind) {
13
+ const settings = table.store?.settings ?? {};
14
+ return {
15
+ kind,
16
+ target: quoteName(fullNameText(table.fullName)),
17
+ constraints: table.expectations.map((item) => ({
18
+ name: quoteName(item.name),
19
+ check: item.check,
20
+ on_violation: onViolation[item.action],
21
+ })),
22
+ comment: table.contract.description ? sqlString(table.contract.description) : "",
23
+ cluster: clusterOf(settings.cluster_by),
24
+ properties: Object.entries((settings.properties ?? {})).map(([key, value]) => ({ key: sqlString(key), value: sqlString(value) })),
25
+ };
26
+ }
27
+ function clusterOf(value) {
28
+ if (value === "auto")
29
+ return "AUTO";
30
+ if (Array.isArray(value))
31
+ return `(${value.map((column) => quoteName(String(column))).join(", ")})`;
32
+ return "";
33
+ }
34
+ /** The `read_files` call for a flow table's files. */
35
+ export function sourceData(table, stream) {
36
+ return {
37
+ stream,
38
+ select: selectOf(table),
39
+ path: sqlString(pathOf(table)),
40
+ options: optionsOf(table),
41
+ };
42
+ }
43
+ /** The columns read under another name at the source, unless the ingest step renamed them. */
44
+ function renamedColumns(table) {
45
+ if (table.ingest?.renamed)
46
+ return [];
47
+ return table.contract.columns.filter((column) => column.physicalName && column.physicalName !== column.name);
48
+ }
49
+ /** `*`, or `* EXCEPT (<source names>), <source> AS <name>, …` for renamed columns. */
50
+ function selectOf(table) {
51
+ const renamed = renamedColumns(table);
52
+ if (renamed.length === 0)
53
+ return "*";
54
+ const sources = renamed.map((column) => quoteName(column.physicalName));
55
+ const aliases = renamed.map((column) => `${quoteName(column.physicalName)} AS ${quoteName(column.name)}`);
56
+ return `* EXCEPT (${sources.join(", ")}), ${aliases.join(", ")}`;
57
+ }
58
+ /** The connection's `path` joined with the table's object. */
59
+ function pathOf(table) {
60
+ // Files copied by another pack (such as adf-copy) are read where it lands them.
61
+ const ingest = table.ingest;
62
+ const folder = ingest?.settings.landing ?? ingest?.connection.settings.path ?? "";
63
+ const base = String(folder).replace(/\/+$/, "");
64
+ const object = (table.ingest?.object ?? "").replace(/^\/+/, "");
65
+ return `${base}/${object}`;
66
+ }
67
+ /** Format, schema hints from the contract, schema evolution, then the user's own options. */
68
+ function optionsOf(table) {
69
+ const settings = table.ingest?.settings ?? {};
70
+ const options = [
71
+ { key: "format", value: sqlString(String(settings.format)) },
72
+ ];
73
+ // Hints name the columns as the files have them.
74
+ const source = new Set(renamedColumns(table));
75
+ const hints = table.contract.columns
76
+ .filter((column) => column.type)
77
+ .map((column) => {
78
+ const name = source.has(column) ? column.physicalName : column.name;
79
+ return `${quoteName(name)} ${column.type}`;
80
+ });
81
+ if (settings.schema_hints === true && hints.length > 0)
82
+ options.push({ key: "schemaHints", value: sqlString(hints.join(", ")) });
83
+ if (settings.schema_evolution)
84
+ options.push({
85
+ key: "schemaEvolutionMode",
86
+ value: sqlString(String(settings.schema_evolution)),
87
+ });
88
+ for (const [key, value] of Object.entries((settings.options ?? {}))) {
89
+ options.push({ key, value: typeof value === "string" ? sqlString(value) : String(value) });
90
+ }
91
+ return options;
92
+ }
93
+ /** The AUTO CDC parts of a change-data table. */
94
+ export function changeData(table) {
95
+ const settings = table.store?.settings ?? {};
96
+ const changes = {
97
+ ...table.fullName,
98
+ table: `${table.fullName.table}${String(settings.changes_suffix)}`,
99
+ };
100
+ const except = (settings.except_columns ?? []);
101
+ return {
102
+ changes: quoteName(fullNameText(changes)),
103
+ flow: quoteName(`${table.fullName.table}_cdc`),
104
+ keys: table.contract.primaryKey.map(quoteName).join(", "),
105
+ sequence: quoteName(String(settings.sequence_by)),
106
+ delete_when: typeof settings.delete_when === "string" ? settings.delete_when : "",
107
+ except_columns: except.map(quoteName).join(", "),
108
+ scd: settings.mode === "history" ? 2 : 1,
109
+ };
110
+ }
111
+ //# sourceMappingURL=sql-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-data.js","sourceRoot":"","sources":["../src/sql-data.ts"],"names":[],"mappings":"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"}
package/dist/sql.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { ResolvedTable } from "@ingestron/definitions";
2
+ /** The templates folder, also used by the template parity check. */
3
+ export declare const templatesFolder: string;
4
+ /** Which template renders a table's file, its data, and the file's extension. */
5
+ export interface SqlFile {
6
+ template: string;
7
+ data: object;
8
+ extension: "sql" | "py";
9
+ }
10
+ /** The SQL or Python file for one table; `header` is its first comment line. */
11
+ export declare function tableSql(table: ResolvedTable, header: string): string;
12
+ /** Renders a file chosen by `sqlFile`. */
13
+ export declare function renderSql({ template, data }: SqlFile): string;
14
+ /** The after-refresh Python file, with its statements and checks as JSON. */
15
+ export declare function renderApply(data: {
16
+ header: string;
17
+ statements: string[];
18
+ checks: object[];
19
+ }): string;
20
+ /** Chooses the template by transform and store method, and builds its data. */
21
+ export declare function sqlFile(table: ResolvedTable, header: string): SqlFile;
22
+ //# sourceMappingURL=sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAM5D,oEAAoE;AACpE,eAAO,MAAM,eAAe,QAAiE,CAAC;AAS9F,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,KAAK,GAAG,IAAI,CAAC;CACzB;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,OAAO,GAAG,MAAM,CAE7D;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,GAAG,MAAM,CAUT;AAED,+EAA+E;AAC/E,wBAAgB,OAAO,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAYrE"}
package/dist/sql.js ADDED
@@ -0,0 +1,81 @@
1
+ // Renders each table's SQL file from the templates in ../templates.
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Liquid } from "liquidjs";
5
+ import { renderQuery } from "./query.js";
6
+ import { pythonData } from "./python-data.js";
7
+ import { changeData, sourceData, tableData } from "./sql-data.js";
8
+ /** The templates folder, also used by the template parity check. */
9
+ export const templatesFolder = join(dirname(fileURLToPath(import.meta.url)), "../templates/");
10
+ const engine = new Liquid({
11
+ root: templatesFolder,
12
+ extname: ".liquid",
13
+ strictVariables: true,
14
+ strictFilters: true,
15
+ });
16
+ /** The SQL or Python file for one table; `header` is its first comment line. */
17
+ export function tableSql(table, header) {
18
+ return renderSql(sqlFile(table, header));
19
+ }
20
+ /** Renders a file chosen by `sqlFile`. */
21
+ export function renderSql({ template, data }) {
22
+ return `${engine.renderFileSync(template, data).trimEnd()}\n`;
23
+ }
24
+ /** The after-refresh Python file, with its statements and checks as JSON. */
25
+ export function renderApply(data) {
26
+ return renderSql({
27
+ template: "apply-contract.py.liquid",
28
+ data: {
29
+ header: data.header,
30
+ statements: JSON.stringify(data.statements, null, 2),
31
+ checks: JSON.stringify(data.checks, null, 2),
32
+ },
33
+ extension: "py",
34
+ });
35
+ }
36
+ /** Chooses the template by transform and store method, and builds its data. */
37
+ export function sqlFile(table, header) {
38
+ if (table.transform?.method === "pyspark") {
39
+ return {
40
+ template: "materialised-view.py.liquid",
41
+ data: pythonData(table, header),
42
+ extension: "py",
43
+ };
44
+ }
45
+ const store = table.store?.method;
46
+ if (store === "streaming-table")
47
+ return streamingTable(table, header);
48
+ if (store === "materialised-view")
49
+ return materialisedView(table, header);
50
+ throw new Error(`No SQL for store method "${String(store)}" (table ${table.name}).`);
51
+ }
52
+ function streamingTable(table, header) {
53
+ const data = {
54
+ header,
55
+ table: tableData(table, "STREAMING TABLE"),
56
+ source: sourceData(table, true),
57
+ };
58
+ if (table.store?.settings.mode === "append")
59
+ return { template: "streaming-table.sql.liquid", data, extension: "sql" };
60
+ return {
61
+ template: "change-data.sql.liquid",
62
+ data: { ...data, cdc: changeData(table) },
63
+ extension: "sql",
64
+ };
65
+ }
66
+ function materialisedView(table, header) {
67
+ const rendered = table.transform
68
+ ? renderQuery(table.transform.code, table.transform.inputs)
69
+ : { sql: "" };
70
+ // The sql method's check has already reported SQL that cannot be rendered.
71
+ const query = "sql" in rendered ? rendered.sql : "";
72
+ const source = table.ingest
73
+ ? sourceData(table, false)
74
+ : { stream: false, select: "*", path: "", options: [] };
75
+ return {
76
+ template: "materialised-view.sql.liquid",
77
+ data: { header, table: tableData(table, "MATERIALIZED VIEW"), source, query },
78
+ extension: "sql",
79
+ };
80
+ }
81
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAElE,oEAAoE;AACpE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;AAE9F,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;IACxB,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,SAAS;IAClB,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,IAAI;CACpB,CAAC,CAAC;AASH,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,KAAoB,EAAE,MAAc;IAC3D,OAAO,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAW;IACnD,OAAO,GAAI,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAY,CAAC,OAAO,EAAE,IAAI,CAAC;AAC5E,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,IAI3B;IACC,OAAO,SAAS,CAAC;QACf,QAAQ,EAAE,0BAA0B;QACpC,IAAI,EAAE;YACJ,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YACpD,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;SAC7C;QACD,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,OAAO,CAAC,KAAoB,EAAE,MAAc;IAC1D,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO;YACL,QAAQ,EAAE,6BAA6B;YACvC,IAAI,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC;YAC/B,SAAS,EAAE,IAAI;SAChB,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC;IAClC,IAAI,KAAK,KAAK,iBAAiB;QAAE,OAAO,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACtE,IAAI,KAAK,KAAK,mBAAmB;QAAE,OAAO,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1E,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,KAAK,CAAC,YAAY,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,cAAc,CAAC,KAAoB,EAAE,MAAc;IAC1D,MAAM,IAAI,GAAG;QACX,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAC1C,MAAM,EAAE,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC;KAChC,CAAC;IACF,IAAI,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,KAAK,QAAQ;QACzC,OAAO,EAAE,QAAQ,EAAE,4BAA4B,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC5E,OAAO;QACL,QAAQ,EAAE,wBAAwB;QAClC,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE;QACzC,SAAS,EAAE,KAAK;KACjB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAoB,EAAE,MAAc;IAC5D,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS;QAC9B,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QAC3D,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAChB,2EAA2E;IAC3E,MAAM,KAAK,GAAG,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;QACzB,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC;QAC1B,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC1D,OAAO;QACL,QAAQ,EAAE,8BAA8B;QACxC,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,mBAAmB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE;QAC7E,SAAS,EAAE,KAAK;KACjB,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,71 @@
1
1
  {
2
2
  "name": "@ingestron/pack-databricks",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "description": "Databricks methods for Ingestron: generates a bundle with a Lakeflow Spark Declarative Pipeline.",
5
+ "license": "Apache-2.0",
6
+ "author": "IntentLabs <hello@ingestron.io>",
7
+ "homepage": "https://docs.ingestron.io",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ingestron/pack-databricks.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/ingestron/pack-databricks/issues",
14
+ "email": "hello@ingestron.io"
15
+ },
16
+ "type": "module",
17
+ "engines": {
18
+ "node": ">=22.13.0"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "templates",
29
+ "README.md",
30
+ "NOTICE"
31
+ ],
32
+ "dependencies": {
33
+ "liquidjs": "10.30.0",
34
+ "yaml": "2.9.1"
35
+ },
36
+ "peerDependencies": {
37
+ "@ingestron/definitions": "^1.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@ingestron/config": "^1.0.0",
41
+ "@ingestron/definitions": "^1.0.0",
42
+ "@microsoft/api-extractor": "7.59.4",
43
+ "@types/node": "22.20.5",
44
+ "ajv": "8.20.0",
45
+ "eslint": "10.13.0",
46
+ "eslint-plugin-jsdoc": "65.4.2",
47
+ "prettier": "3.9.10",
48
+ "tsx": "4.23.15",
49
+ "typescript": "6.0.3",
50
+ "typescript-eslint": "8.72.0",
51
+ "vitest": "5.0.5"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -p tsconfig.build.json",
58
+ "typecheck": "pnpm build && tsc -p tsconfig.json --noEmit",
59
+ "lint": "eslint .",
60
+ "test": "vitest run",
61
+ "check:docs": "tsx scripts/check-docs.ts",
62
+ "validate": "pnpm format:check && pnpm secrets:check && pnpm licences:check && node --test scripts/*.test.mjs && pnpm lint && pnpm typecheck && pnpm test && pnpm check:docs && pnpm check:api",
63
+ "api": "api-extractor run --local",
64
+ "check:api": "api-extractor run",
65
+ "format": "prettier --write .",
66
+ "format:check": "prettier --check .",
67
+ "secrets:check": "node scripts/scan-secrets.mjs",
68
+ "licences:check": "node scripts/check-licences.mjs",
69
+ "dco:check": "node scripts/check-dco.mjs"
70
+ }
6
71
  }
@@ -0,0 +1,58 @@
1
+ # {{ header }}
2
+ # Runs after each refresh: Lakeflow pipelines cannot set tags or column
3
+ # comments, nor compare counts over all rows, so this task does.
4
+ import json
5
+
6
+ from pyspark.sql import SparkSession
7
+
8
+ STATEMENTS = json.loads(r"""{{ statements }}""")
9
+ CHECKS = json.loads(r"""{{ checks }}""")
10
+
11
+
12
+ def passes(operator, expected, measured):
13
+ """True when the measured count meets the rule's comparison."""
14
+ if operator == "mustBe":
15
+ return measured == expected
16
+ if operator == "mustNotBe":
17
+ return measured != expected
18
+ if operator == "mustBeGreaterThan":
19
+ return measured > expected
20
+ if operator == "mustBeGreaterOrEqualTo":
21
+ return measured >= expected
22
+ if operator == "mustBeLessThan":
23
+ return measured < expected
24
+ if operator == "mustBeLessOrEqualTo":
25
+ return measured <= expected
26
+ low, high = expected
27
+ inside = low <= measured <= high
28
+ return inside if operator == "mustBeBetween" else not inside
29
+
30
+
31
+ def measure(spark, check):
32
+ """The rule's count, in rows or as a percentage of all rows."""
33
+ row = spark.sql(check["query"]).first()
34
+ counted, total = row["counted"] or 0, row["total"] or 0
35
+ if check["unit"] == "percent":
36
+ return 0 if total == 0 else counted * 100 / total
37
+ return counted
38
+
39
+
40
+ def main():
41
+ spark = SparkSession.builder.getOrCreate()
42
+ for statement in STATEMENTS:
43
+ spark.sql(statement)
44
+ failures = []
45
+ for check in CHECKS:
46
+ measured = measure(spark, check)
47
+ if passes(check["operator"], check["value"], measured):
48
+ continue
49
+ message = f'{check["table"]}: {check["name"]} measured {measured}, expected {check["operator"]} {check["value"]}'
50
+ if check["action"] == "fail":
51
+ failures.append(message)
52
+ else:
53
+ print(f"Warning: {message}")
54
+ if failures:
55
+ raise RuntimeError("Table checks failed:\n" + "\n".join(failures))
56
+
57
+
58
+ main()
@@ -0,0 +1,20 @@
1
+ -- {{ header }}
2
+ -- Changes as received, each read once.
3
+ CREATE OR REFRESH STREAMING TABLE {{ cdc.changes }}
4
+ AS {% render "read-files", source: source %};
5
+
6
+ -- The current rows{% if cdc.scd == 2 %}, with history{% endif %}, kept by key.
7
+ {% render "table", table: table %};
8
+
9
+ CREATE FLOW {{ cdc.flow }}
10
+ AS AUTO CDC INTO {{ table.target }}
11
+ FROM STREAM({{ cdc.changes }})
12
+ KEYS ({{ cdc.keys }})
13
+ {%- if cdc.delete_when != "" %}
14
+ APPLY AS DELETE WHEN {{ cdc.delete_when }}
15
+ {%- endif %}
16
+ SEQUENCE BY {{ cdc.sequence }}
17
+ {%- if cdc.except_columns != "" %}
18
+ COLUMNS * EXCEPT ({{ cdc.except_columns }})
19
+ {%- endif %}
20
+ STORED AS SCD TYPE {{ cdc.scd }};
@@ -0,0 +1,28 @@
1
+ # {{ header }}
2
+ from pyspark import pipelines as dp
3
+
4
+ # The transform's own code, unchanged:
5
+ {{ code }}
6
+
7
+
8
+ @dp.materialized_view(
9
+ name={{ name }},
10
+ {%- if comment != "" %}
11
+ comment={{ comment }},
12
+ {%- endif %}
13
+ {%- if cluster != "" %}
14
+ {{ cluster }},
15
+ {%- endif %}
16
+ {%- if properties != "" %}
17
+ table_properties={{ properties }},
18
+ {%- endif %}
19
+ )
20
+ {%- for group in expectations %}
21
+ @dp.{{ group.decorator }}({{ group.checks }})
22
+ {%- endfor %}
23
+ def {{ wrapper }}():
24
+ return {{ function }}({
25
+ {%- for input in inputs %}
26
+ {{ input.name }}: spark.read.table({{ input.table }}),
27
+ {%- endfor %}
28
+ })
@@ -0,0 +1,4 @@
1
+ -- {{ header }}
2
+ {% render "table", table: table %}
3
+ AS
4
+ {% if query != "" %}{{ query }}{% else %}{% render "read-files", source: source %}{% endif %};
@@ -0,0 +1,7 @@
1
+ {%- comment %} Ends without a newline: the caller continues after it. {% endcomment -%}
2
+ SELECT {{ source.select }} FROM {% if source.stream %}STREAM {% endif %}read_files(
3
+ {{ source.path }}
4
+ {%- for option in source.options %},
5
+ {{ option.key }} => {{ option.value }}
6
+ {%- endfor %}
7
+ ){%- comment %}{% endcomment -%}
@@ -0,0 +1,3 @@
1
+ -- {{ header }}
2
+ {% render "table", table: table %}
3
+ AS {% render "read-files", source: source %};
@@ -0,0 +1,17 @@
1
+ {%- comment %} Ends without a newline: the caller continues after it. {% endcomment -%}
2
+ CREATE OR REFRESH {{ table.kind }} {{ table.target }}
3
+ {%- if table.constraints.size > 0 %} (
4
+ {%- for constraint in table.constraints %}
5
+ CONSTRAINT {{ constraint.name }} EXPECT ({{ constraint.check }}){{ constraint.on_violation }}{% unless forloop.last %},{% endunless %}
6
+ {%- endfor %}
7
+ )
8
+ {%- endif %}
9
+ {%- if table.comment != "" %}
10
+ COMMENT {{ table.comment }}
11
+ {%- endif %}
12
+ {%- if table.cluster != "" %}
13
+ CLUSTER BY {{ table.cluster }}
14
+ {%- endif %}
15
+ {%- if table.properties.size > 0 %}
16
+ TBLPROPERTIES ({% for property in table.properties %}{{ property.key }} = {{ property.value }}{% unless forloop.last %}, {% endunless %}{% endfor %})
17
+ {%- endif -%}