@lightdash/cli 0.2395.0 → 0.2396.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.
@@ -0,0 +1,9 @@
1
+ type SqlHandlerOptions = {
2
+ output: string;
3
+ limit?: number;
4
+ pageSize?: number;
5
+ verbose?: boolean;
6
+ };
7
+ export declare const sqlHandler: (sql: string, options: SqlHandlerOptions) => Promise<void>;
8
+ export {};
9
+ //# sourceMappingURL=sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/handlers/sql.ts"],"names":[],"mappings":"AAYA,KAAK,iBAAiB,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA0FF,eAAO,MAAM,UAAU,QACd,MAAM,WACF,iBAAiB,KAC3B,OAAO,CAAC,IAAI,CAiEd,CAAC"}
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sqlHandler = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@lightdash/common");
6
+ const fs_1 = require("fs");
7
+ const config_1 = require("../config");
8
+ const globalState_1 = tslib_1.__importDefault(require("../globalState"));
9
+ const styles = tslib_1.__importStar(require("../styles"));
10
+ const apiClient_1 = require("./dbt/apiClient");
11
+ const DEFAULT_PAGE_SIZE = 500;
12
+ const POLL_INTERVAL_MS = 500;
13
+ /**
14
+ * Convert ResultRow array to CSV string
15
+ */
16
+ function resultsToCsv(columns, rows) {
17
+ // Escape CSV value: wrap in quotes if contains comma, quote, or newline
18
+ const escapeValue = (value) => {
19
+ if (value === null || value === undefined) {
20
+ return '';
21
+ }
22
+ const str = String(value);
23
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
24
+ return `"${str.replace(/"/g, '""')}"`;
25
+ }
26
+ return str;
27
+ };
28
+ const header = columns.map(escapeValue).join(',');
29
+ const dataRows = rows.map((row) => columns.map((col) => escapeValue(row[col]?.value?.raw)).join(','));
30
+ return [header, ...dataRows].join('\n');
31
+ }
32
+ /**
33
+ * Fetch query results once
34
+ */
35
+ async function fetchQueryResults(projectUuid, queryUuid, pageSize) {
36
+ const url = `/api/v2/projects/${projectUuid}/query/${queryUuid}?pageSize=${pageSize}`;
37
+ return (0, apiClient_1.lightdashApi)({
38
+ method: 'GET',
39
+ url,
40
+ body: undefined,
41
+ });
42
+ }
43
+ /**
44
+ * Sleep for a given duration
45
+ */
46
+ function sleep(ms) {
47
+ return new Promise((resolve) => {
48
+ setTimeout(resolve, ms);
49
+ });
50
+ }
51
+ /**
52
+ * Poll for query results until status is READY or ERROR
53
+ */
54
+ async function pollQueryResults(projectUuid, queryUuid, pageSize) {
55
+ const poll = async () => {
56
+ const result = await fetchQueryResults(projectUuid, queryUuid, pageSize);
57
+ globalState_1.default.debug(`> Query status: ${result.status}`);
58
+ if (result.status === common_1.QueryHistoryStatus.READY) {
59
+ return result;
60
+ }
61
+ if (result.status === common_1.QueryHistoryStatus.ERROR) {
62
+ return result;
63
+ }
64
+ if (result.status === common_1.QueryHistoryStatus.CANCELLED) {
65
+ throw new Error('Query was cancelled');
66
+ }
67
+ // Still pending, wait and poll again
68
+ await sleep(POLL_INTERVAL_MS);
69
+ return poll();
70
+ };
71
+ return poll();
72
+ }
73
+ const sqlHandler = async (sql, options) => {
74
+ globalState_1.default.setVerbose(options.verbose ?? false);
75
+ const config = await (0, config_1.getConfig)();
76
+ const projectUuid = config.context?.project;
77
+ if (!projectUuid) {
78
+ throw new Error(`No project selected. Run 'lightdash config set-project' first.`);
79
+ }
80
+ globalState_1.default.debug(`> Running SQL query against project: ${projectUuid}`);
81
+ globalState_1.default.debug(`> SQL: ${sql}`);
82
+ const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
83
+ // Submit the query
84
+ const spinner = globalState_1.default.startSpinner('Submitting SQL query...');
85
+ const submitResult = await (0, apiClient_1.lightdashApi)({
86
+ method: 'POST',
87
+ url: `/api/v2/projects/${projectUuid}/query/sql`,
88
+ body: JSON.stringify({
89
+ sql,
90
+ limit: options.limit,
91
+ context: 'cli',
92
+ }),
93
+ });
94
+ globalState_1.default.debug(`> Query UUID: ${submitResult.queryUuid}`);
95
+ spinner.text = 'Waiting for query results...';
96
+ // Poll for results
97
+ const result = await pollQueryResults(projectUuid, submitResult.queryUuid, pageSize);
98
+ if (result.status === common_1.QueryHistoryStatus.ERROR) {
99
+ spinner.fail('Query failed');
100
+ throw new Error(result.error ?? 'Query execution failed');
101
+ }
102
+ if (result.status !== common_1.QueryHistoryStatus.READY) {
103
+ spinner.fail('Unexpected query status');
104
+ throw new Error(`Unexpected query status: ${result.status}`);
105
+ }
106
+ // Extract column names from the columns metadata
107
+ const columns = Object.keys(result.columns);
108
+ const rowCount = result.rows.length;
109
+ spinner.text = `Writing ${rowCount} rows to ${options.output}...`;
110
+ // Convert to CSV and write to file
111
+ const csv = resultsToCsv(columns, result.rows);
112
+ await fs_1.promises.writeFile(options.output, csv, 'utf8');
113
+ spinner.succeed(`${styles.success('Success!')} Wrote ${rowCount} rows to ${options.output}`);
114
+ };
115
+ exports.sqlHandler = sqlHandler;
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ const login_1 = require("./handlers/login");
21
21
  const preview_1 = require("./handlers/preview");
22
22
  const renameHandler_1 = require("./handlers/renameHandler");
23
23
  const setProject_1 = require("./handlers/setProject");
24
+ const sql_1 = require("./handlers/sql");
24
25
  const validate_1 = require("./handlers/validate");
25
26
  const styles = tslib_1.__importStar(require("./styles"));
26
27
  // Trigger CLI tests
@@ -423,6 +424,15 @@ ${styles.bold('Examples:')}
423
424
  .option('--verbose', 'Show detailed output', false)
424
425
  .option('-f, --format <format>', 'Output format: cli (default) or json (SARIF format)', 'cli')
425
426
  .action(lint_1.lintHandler);
427
+ commander_1.program
428
+ .command('sql')
429
+ .description('Run raw SQL query against the warehouse using project credentials')
430
+ .argument('<query>', 'SQL query to execute')
431
+ .requiredOption('-o, --output <file>', 'Output file path for CSV results')
432
+ .option('--limit <number>', 'Maximum rows to return from query', parseIntArgument)
433
+ .option('--page-size <number>', 'Number of rows per page (default: 500, max: 5000)', parseIntArgument)
434
+ .option('--verbose', 'Show detailed output', false)
435
+ .action(sql_1.sqlHandler);
426
436
  const errorHandler = (err) => {
427
437
  // Use error message with fallback for safety
428
438
  const errorMessage = (0, common_1.getErrorMessage)(err) || 'An unexpected error occurred';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "0.2395.0",
3
+ "version": "0.2396.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,8 +38,8 @@
38
38
  "unique-names-generator": "^4.7.1",
39
39
  "uuid": "^11.0.3",
40
40
  "yaml": "^2.7.0",
41
- "@lightdash/common": "0.2395.0",
42
- "@lightdash/warehouses": "0.2395.0"
41
+ "@lightdash/common": "0.2396.0",
42
+ "@lightdash/warehouses": "0.2396.0"
43
43
  },
44
44
  "description": "Lightdash CLI tool",
45
45
  "devDependencies": {