Skip to content

Commit 3cddd0e

Browse files
authored
feat: add typed sql parameter helpers (#9)
* feat: add typed sql parameter helpers * feat: sql.binary() returns STRING with hex encoding and adds an interactive SQL helpers demo
1 parent 15575d2 commit 3cddd0e

File tree

28 files changed

+1456
-254
lines changed

28 files changed

+1456
-254
lines changed

apps/dev-playground/client/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import globals from "globals";
66
import tseslint from "typescript-eslint";
77

88
export default defineConfig([
9-
globalIgnores(["dist"]),
9+
globalIgnores(["dist", "src/routeTree.gen.ts"]),
1010
{
1111
files: ["**/*.{ts,tsx}"],
1212
extends: [

apps/dev-playground/client/src/appKitTypes.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,15 @@ declare module "@databricks/app-kit-ui/react" {
6161
app_name: string;
6262
total_cost_usd: number;
6363
}[];
64+
sql_helpers_test: {
65+
string_value: string;
66+
number_value: number;
67+
boolean_value: boolean;
68+
date_value: string;
69+
timestamp_value: string;
70+
binary_value: string;
71+
binary_hex: string;
72+
binary_length: number;
73+
};
6474
}
6575
}

apps/dev-playground/client/src/components/analytics/usage-trends-chart.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { sql } from "@databricks/app-kit-ui/js";
12
import { BarChart } from "@databricks/app-kit-ui/react";
23
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
34
import {
@@ -21,7 +22,7 @@ export function UsageTrendsChart({
2122
}: UsageTrendsChartProps) {
2223
const spendDataParams = {
2324
...queryParams,
24-
groupBy,
25+
groupBy: sql.string(groupBy),
2526
};
2627

2728
return (

apps/dev-playground/client/src/lib/utils/filter-utils.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { sql } from "@databricks/app-kit-ui/js";
12
import type { Aggregation, DashboardFilters } from "@/lib/types";
23

34
const ALLOWED_PERIODS = ["day", "week", "month"] as const;
@@ -56,14 +57,16 @@ export function buildWorkflowParams(
5657
) {
5758
const { startDate, endDate } = getDateRange(filters);
5859

60+
const aggregationLevel = sanitizeAggregationLevel(aggregation.period);
61+
5962
return {
6063
filters: {
61-
startDate: startDate.toISOString().split("T")[0],
62-
endDate: endDate.toISOString().split("T")[0],
63-
aggregationLevel: sanitizeAggregationLevel(aggregation.period),
64-
appId: filters.apps !== "all" ? filters.apps : "all",
65-
creator: filters.creator !== "all" ? filters.creator : "all",
66-
groupBy: "default",
64+
startDate: sql.date(startDate),
65+
endDate: sql.date(endDate),
66+
aggregationLevel: sql.string(aggregationLevel),
67+
appId: sql.string(filters.apps !== "all" ? filters.apps : "all"),
68+
creator: sql.string(filters.creator !== "all" ? filters.creator : "all"),
69+
groupBy: sql.string("default"),
6770
},
6871
};
6972
}

apps/dev-playground/client/src/routeTree.gen.ts

Lines changed: 121 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -8,125 +8,150 @@
88
// You should NOT make any changes in this file as it will be overwritten.
99
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
1010

11-
import { Route as rootRouteImport } from "./routes/__root";
12-
import { Route as TelemetryRouteRouteImport } from "./routes/telemetry.route";
13-
import { Route as ReconnectRouteRouteImport } from "./routes/reconnect.route";
14-
import { Route as DataVisualizationRouteRouteImport } from "./routes/data-visualization.route";
15-
import { Route as AnalyticsRouteRouteImport } from "./routes/analytics.route";
16-
import { Route as IndexRouteImport } from "./routes/index";
11+
import { Route as rootRouteImport } from './routes/__root'
12+
import { Route as TelemetryRouteRouteImport } from './routes/telemetry.route'
13+
import { Route as SqlHelpersRouteRouteImport } from './routes/sql-helpers.route'
14+
import { Route as ReconnectRouteRouteImport } from './routes/reconnect.route'
15+
import { Route as DataVisualizationRouteRouteImport } from './routes/data-visualization.route'
16+
import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route'
17+
import { Route as IndexRouteImport } from './routes/index'
1718

1819
const TelemetryRouteRoute = TelemetryRouteRouteImport.update({
19-
id: "/telemetry",
20-
path: "/telemetry",
20+
id: '/telemetry',
21+
path: '/telemetry',
2122
getParentRoute: () => rootRouteImport,
22-
} as any);
23+
} as any)
24+
const SqlHelpersRouteRoute = SqlHelpersRouteRouteImport.update({
25+
id: '/sql-helpers',
26+
path: '/sql-helpers',
27+
getParentRoute: () => rootRouteImport,
28+
} as any)
2329
const ReconnectRouteRoute = ReconnectRouteRouteImport.update({
24-
id: "/reconnect",
25-
path: "/reconnect",
30+
id: '/reconnect',
31+
path: '/reconnect',
2632
getParentRoute: () => rootRouteImport,
27-
} as any);
33+
} as any)
2834
const DataVisualizationRouteRoute = DataVisualizationRouteRouteImport.update({
29-
id: "/data-visualization",
30-
path: "/data-visualization",
35+
id: '/data-visualization',
36+
path: '/data-visualization',
3137
getParentRoute: () => rootRouteImport,
32-
} as any);
38+
} as any)
3339
const AnalyticsRouteRoute = AnalyticsRouteRouteImport.update({
34-
id: "/analytics",
35-
path: "/analytics",
40+
id: '/analytics',
41+
path: '/analytics',
3642
getParentRoute: () => rootRouteImport,
37-
} as any);
43+
} as any)
3844
const IndexRoute = IndexRouteImport.update({
39-
id: "/",
40-
path: "/",
45+
id: '/',
46+
path: '/',
4147
getParentRoute: () => rootRouteImport,
42-
} as any);
48+
} as any)
4349

4450
export interface FileRoutesByFullPath {
45-
"/": typeof IndexRoute;
46-
"/analytics": typeof AnalyticsRouteRoute;
47-
"/data-visualization": typeof DataVisualizationRouteRoute;
48-
"/reconnect": typeof ReconnectRouteRoute;
49-
"/telemetry": typeof TelemetryRouteRoute;
51+
'/': typeof IndexRoute
52+
'/analytics': typeof AnalyticsRouteRoute
53+
'/data-visualization': typeof DataVisualizationRouteRoute
54+
'/reconnect': typeof ReconnectRouteRoute
55+
'/sql-helpers': typeof SqlHelpersRouteRoute
56+
'/telemetry': typeof TelemetryRouteRoute
5057
}
5158
export interface FileRoutesByTo {
52-
"/": typeof IndexRoute;
53-
"/analytics": typeof AnalyticsRouteRoute;
54-
"/data-visualization": typeof DataVisualizationRouteRoute;
55-
"/reconnect": typeof ReconnectRouteRoute;
56-
"/telemetry": typeof TelemetryRouteRoute;
59+
'/': typeof IndexRoute
60+
'/analytics': typeof AnalyticsRouteRoute
61+
'/data-visualization': typeof DataVisualizationRouteRoute
62+
'/reconnect': typeof ReconnectRouteRoute
63+
'/sql-helpers': typeof SqlHelpersRouteRoute
64+
'/telemetry': typeof TelemetryRouteRoute
5765
}
5866
export interface FileRoutesById {
59-
__root__: typeof rootRouteImport;
60-
"/": typeof IndexRoute;
61-
"/analytics": typeof AnalyticsRouteRoute;
62-
"/data-visualization": typeof DataVisualizationRouteRoute;
63-
"/reconnect": typeof ReconnectRouteRoute;
64-
"/telemetry": typeof TelemetryRouteRoute;
67+
__root__: typeof rootRouteImport
68+
'/': typeof IndexRoute
69+
'/analytics': typeof AnalyticsRouteRoute
70+
'/data-visualization': typeof DataVisualizationRouteRoute
71+
'/reconnect': typeof ReconnectRouteRoute
72+
'/sql-helpers': typeof SqlHelpersRouteRoute
73+
'/telemetry': typeof TelemetryRouteRoute
6574
}
6675
export interface FileRouteTypes {
67-
fileRoutesByFullPath: FileRoutesByFullPath;
76+
fileRoutesByFullPath: FileRoutesByFullPath
6877
fullPaths:
69-
| "/"
70-
| "/analytics"
71-
| "/data-visualization"
72-
| "/reconnect"
73-
| "/telemetry";
74-
fileRoutesByTo: FileRoutesByTo;
75-
to: "/" | "/analytics" | "/data-visualization" | "/reconnect" | "/telemetry";
78+
| '/'
79+
| '/analytics'
80+
| '/data-visualization'
81+
| '/reconnect'
82+
| '/sql-helpers'
83+
| '/telemetry'
84+
fileRoutesByTo: FileRoutesByTo
85+
to:
86+
| '/'
87+
| '/analytics'
88+
| '/data-visualization'
89+
| '/reconnect'
90+
| '/sql-helpers'
91+
| '/telemetry'
7692
id:
77-
| "__root__"
78-
| "/"
79-
| "/analytics"
80-
| "/data-visualization"
81-
| "/reconnect"
82-
| "/telemetry";
83-
fileRoutesById: FileRoutesById;
93+
| '__root__'
94+
| '/'
95+
| '/analytics'
96+
| '/data-visualization'
97+
| '/reconnect'
98+
| '/sql-helpers'
99+
| '/telemetry'
100+
fileRoutesById: FileRoutesById
84101
}
85102
export interface RootRouteChildren {
86-
IndexRoute: typeof IndexRoute;
87-
AnalyticsRouteRoute: typeof AnalyticsRouteRoute;
88-
DataVisualizationRouteRoute: typeof DataVisualizationRouteRoute;
89-
ReconnectRouteRoute: typeof ReconnectRouteRoute;
90-
TelemetryRouteRoute: typeof TelemetryRouteRoute;
103+
IndexRoute: typeof IndexRoute
104+
AnalyticsRouteRoute: typeof AnalyticsRouteRoute
105+
DataVisualizationRouteRoute: typeof DataVisualizationRouteRoute
106+
ReconnectRouteRoute: typeof ReconnectRouteRoute
107+
SqlHelpersRouteRoute: typeof SqlHelpersRouteRoute
108+
TelemetryRouteRoute: typeof TelemetryRouteRoute
91109
}
92110

93-
declare module "@tanstack/react-router" {
111+
declare module '@tanstack/react-router' {
94112
interface FileRoutesByPath {
95-
"/telemetry": {
96-
id: "/telemetry";
97-
path: "/telemetry";
98-
fullPath: "/telemetry";
99-
preLoaderRoute: typeof TelemetryRouteRouteImport;
100-
parentRoute: typeof rootRouteImport;
101-
};
102-
"/reconnect": {
103-
id: "/reconnect";
104-
path: "/reconnect";
105-
fullPath: "/reconnect";
106-
preLoaderRoute: typeof ReconnectRouteRouteImport;
107-
parentRoute: typeof rootRouteImport;
108-
};
109-
"/data-visualization": {
110-
id: "/data-visualization";
111-
path: "/data-visualization";
112-
fullPath: "/data-visualization";
113-
preLoaderRoute: typeof DataVisualizationRouteRouteImport;
114-
parentRoute: typeof rootRouteImport;
115-
};
116-
"/analytics": {
117-
id: "/analytics";
118-
path: "/analytics";
119-
fullPath: "/analytics";
120-
preLoaderRoute: typeof AnalyticsRouteRouteImport;
121-
parentRoute: typeof rootRouteImport;
122-
};
123-
"/": {
124-
id: "/";
125-
path: "/";
126-
fullPath: "/";
127-
preLoaderRoute: typeof IndexRouteImport;
128-
parentRoute: typeof rootRouteImport;
129-
};
113+
'/telemetry': {
114+
id: '/telemetry'
115+
path: '/telemetry'
116+
fullPath: '/telemetry'
117+
preLoaderRoute: typeof TelemetryRouteRouteImport
118+
parentRoute: typeof rootRouteImport
119+
}
120+
'/sql-helpers': {
121+
id: '/sql-helpers'
122+
path: '/sql-helpers'
123+
fullPath: '/sql-helpers'
124+
preLoaderRoute: typeof SqlHelpersRouteRouteImport
125+
parentRoute: typeof rootRouteImport
126+
}
127+
'/reconnect': {
128+
id: '/reconnect'
129+
path: '/reconnect'
130+
fullPath: '/reconnect'
131+
preLoaderRoute: typeof ReconnectRouteRouteImport
132+
parentRoute: typeof rootRouteImport
133+
}
134+
'/data-visualization': {
135+
id: '/data-visualization'
136+
path: '/data-visualization'
137+
fullPath: '/data-visualization'
138+
preLoaderRoute: typeof DataVisualizationRouteRouteImport
139+
parentRoute: typeof rootRouteImport
140+
}
141+
'/analytics': {
142+
id: '/analytics'
143+
path: '/analytics'
144+
fullPath: '/analytics'
145+
preLoaderRoute: typeof AnalyticsRouteRouteImport
146+
parentRoute: typeof rootRouteImport
147+
}
148+
'/': {
149+
id: '/'
150+
path: '/'
151+
fullPath: '/'
152+
preLoaderRoute: typeof IndexRouteImport
153+
parentRoute: typeof rootRouteImport
154+
}
130155
}
131156
}
132157

@@ -135,8 +160,9 @@ const rootRouteChildren: RootRouteChildren = {
135160
AnalyticsRouteRoute: AnalyticsRouteRoute,
136161
DataVisualizationRouteRoute: DataVisualizationRouteRoute,
137162
ReconnectRouteRoute: ReconnectRouteRoute,
163+
SqlHelpersRouteRoute: SqlHelpersRouteRoute,
138164
TelemetryRouteRoute: TelemetryRouteRoute,
139-
};
165+
}
140166
export const routeTree = rootRouteImport
141167
._addFileChildren(rootRouteChildren)
142-
._addFileTypes<FileRouteTypes>();
168+
._addFileTypes<FileRouteTypes>()

apps/dev-playground/client/src/routes/__root.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ function RootComponent() {
5656
Telemetry
5757
</Button>
5858
</Link>
59+
<Link to="/sql-helpers" className="no-underline">
60+
<Button
61+
variant="ghost"
62+
className="text-gray-700 hover:text-gray-900"
63+
>
64+
SQL Helpers
65+
</Button>
66+
</Link>
5967
</div>
6068
</nav>
6169
</div>

apps/dev-playground/client/src/routes/data-visualization.route.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { sql } from "@databricks/app-kit-ui/js";
12
import {
23
AreaChart,
34
BarChart,
@@ -58,22 +59,20 @@ function CodeSnippet({ code }: { code: string }) {
5859

5960
function DataVisualizationRoute() {
6061
// Default params for the demo - last 30 days
61-
const endDate = new Date().toISOString().split("T")[0];
62-
const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
63-
.toISOString()
64-
.split("T")[0];
62+
const endDate = new Date();
63+
const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
6564

6665
const commonParams = {
67-
startDate,
68-
endDate,
69-
aggregationLevel: "day",
66+
startDate: sql.date(startDate),
67+
endDate: sql.date(endDate),
68+
aggregationLevel: sql.string("day"),
7069
};
7170

7271
const spendDataParams = {
7372
...commonParams,
74-
appId: "all",
75-
creator: "all",
76-
groupBy: "default",
73+
appId: sql.string("all"),
74+
creator: sql.string("all"),
75+
groupBy: sql.string("default"),
7776
};
7877

7978
return (

apps/dev-playground/client/src/routes/index.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,25 @@ function IndexRoute() {
103103
</Button>
104104
</div>
105105
</Card>
106+
107+
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer">
108+
<div className="flex flex-col h-full">
109+
<h3 className="text-2xl font-semibold text-gray-900 mb-3">
110+
SQL Helpers
111+
</h3>
112+
<p className="text-gray-600 mb-6 flex-grow">
113+
Type-safe parameter helpers for Databricks SQL queries. Test
114+
each helper interactively and see the generated parameter
115+
objects.
116+
</p>
117+
<Button
118+
onClick={() => navigate({ to: "/sql-helpers" })}
119+
className="w-full"
120+
>
121+
Try SQL Helpers
122+
</Button>
123+
</div>
124+
</Card>
106125
</div>
107126

108127
<div className="text-center pt-12 border-t border-gray-200">

0 commit comments

Comments
 (0)