@duckcodeailabs/dql-core 0.3.0 → 0.3.2

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 (35) hide show
  1. package/dist/ast/nodes.d.ts +2 -0
  2. package/dist/ast/nodes.d.ts.map +1 -1
  3. package/dist/lexer/token.d.ts +1 -0
  4. package/dist/lexer/token.d.ts.map +1 -1
  5. package/dist/lexer/token.js +2 -0
  6. package/dist/lexer/token.js.map +1 -1
  7. package/dist/parser/parser.d.ts.map +1 -1
  8. package/dist/parser/parser.js +13 -1
  9. package/dist/parser/parser.js.map +1 -1
  10. package/dist/semantic/analyzer.d.ts.map +1 -1
  11. package/dist/semantic/analyzer.js +7 -6
  12. package/dist/semantic/analyzer.js.map +1 -1
  13. package/dist/semantic/index.d.ts +4 -2
  14. package/dist/semantic/index.d.ts.map +1 -1
  15. package/dist/semantic/index.js +2 -1
  16. package/dist/semantic/index.js.map +1 -1
  17. package/dist/semantic/providers/index.d.ts +6 -1
  18. package/dist/semantic/providers/index.d.ts.map +1 -1
  19. package/dist/semantic/providers/index.js +3 -1
  20. package/dist/semantic/providers/index.js.map +1 -1
  21. package/dist/semantic/providers/provider.d.ts +11 -1
  22. package/dist/semantic/providers/provider.d.ts.map +1 -1
  23. package/dist/semantic/providers/registry.d.ts +11 -2
  24. package/dist/semantic/providers/registry.d.ts.map +1 -1
  25. package/dist/semantic/providers/registry.js +73 -23
  26. package/dist/semantic/providers/registry.js.map +1 -1
  27. package/dist/semantic/providers/repo-resolver.d.ts +32 -0
  28. package/dist/semantic/providers/repo-resolver.d.ts.map +1 -0
  29. package/dist/semantic/providers/repo-resolver.js +169 -0
  30. package/dist/semantic/providers/repo-resolver.js.map +1 -0
  31. package/dist/semantic/providers/snowflake-provider.d.ts +55 -0
  32. package/dist/semantic/providers/snowflake-provider.d.ts.map +1 -0
  33. package/dist/semantic/providers/snowflake-provider.js +246 -0
  34. package/dist/semantic/providers/snowflake-provider.js.map +1 -0
  35. package/package.json +1 -1
@@ -7,45 +7,95 @@ import { join } from 'node:path';
7
7
  import { DqlProvider } from './dql-provider.js';
8
8
  import { DbtProvider } from './dbt-provider.js';
9
9
  import { CubejsProvider } from './cubejs-provider.js';
10
+ import { resolveRepoSource } from './repo-resolver.js';
10
11
  /**
11
12
  * Resolve the semantic layer from config, or auto-detect if no config is given.
12
13
  *
13
14
  * Auto-detection order:
14
- * 1. If `semantic-layer/` exists in projectRoot, use DQL native provider.
15
- * 2. Otherwise return undefined.
15
+ * 1. If explicit config is provided, use that provider.
16
+ * 2. If `semantic-layer/` exists in projectRoot, use DQL native provider.
17
+ * 3. If `dbt_project.yml` exists in projectRoot, use dbt provider.
18
+ * 4. If `model/` or `schema/` with cube YAML exists, use cubejs provider.
19
+ * 5. Otherwise return undefined.
16
20
  */
17
21
  export function resolveSemanticLayer(config, projectRoot) {
22
+ return resolveSemanticLayerWithDiagnostics(config, projectRoot).layer;
23
+ }
24
+ export function resolveSemanticLayerWithDiagnostics(config, projectRoot) {
18
25
  if (config) {
19
26
  return loadFromConfig(config, projectRoot);
20
27
  }
21
- // Auto-detect: check for native DQL semantic-layer directory
28
+ return autoDetect(projectRoot);
29
+ }
30
+ function autoDetect(projectRoot) {
31
+ // 1. DQL native semantic-layer/ directory
22
32
  const nativeDir = join(projectRoot, 'semantic-layer');
23
33
  if (existsSync(nativeDir)) {
24
- const provider = new DqlProvider();
25
- return provider.load({ provider: 'dql' }, projectRoot);
34
+ const result = loadFromConfig({ provider: 'dql' }, projectRoot);
35
+ result.detectedProvider = 'dql';
36
+ return result;
37
+ }
38
+ // 2. dbt project (dbt_project.yml in root)
39
+ if (existsSync(join(projectRoot, 'dbt_project.yml'))) {
40
+ const result = loadFromConfig({ provider: 'dbt' }, projectRoot);
41
+ result.detectedProvider = 'dbt';
42
+ return result;
43
+ }
44
+ // 3. Cube.js project (model/ or schema/ directory)
45
+ for (const candidate of ['model', 'schema']) {
46
+ if (existsSync(join(projectRoot, candidate))) {
47
+ const result = loadFromConfig({ provider: 'cubejs' }, projectRoot);
48
+ result.detectedProvider = 'cubejs';
49
+ return result;
50
+ }
26
51
  }
27
- return undefined;
52
+ return { layer: undefined, errors: [] };
28
53
  }
29
54
  function loadFromConfig(config, projectRoot) {
30
- switch (config.provider) {
31
- case 'dql': {
32
- const provider = new DqlProvider();
33
- return provider.load(config, projectRoot);
34
- }
35
- case 'dbt': {
36
- const provider = new DbtProvider();
37
- return provider.load(config, projectRoot);
55
+ const errors = [];
56
+ try {
57
+ // Resolve remote repo source to a local path if needed
58
+ let effectiveRoot = projectRoot;
59
+ if (config.source && config.source !== 'local' && config.repoUrl) {
60
+ const resolved = resolveRepoSource(config, projectRoot);
61
+ effectiveRoot = resolved.localPath;
62
+ if (resolved.warnings.length > 0) {
63
+ errors.push(...resolved.warnings);
64
+ }
65
+ // When using a remote source, the provider should read from the resolved
66
+ // root directly — clear projectPath so the provider doesn't double-join.
67
+ config = { ...config, projectPath: undefined };
38
68
  }
39
- case 'cubejs': {
40
- const provider = new CubejsProvider();
41
- return provider.load(config, projectRoot);
69
+ switch (config.provider) {
70
+ case 'dql': {
71
+ const provider = new DqlProvider();
72
+ return { layer: provider.load(config, effectiveRoot), errors };
73
+ }
74
+ case 'dbt': {
75
+ const provider = new DbtProvider();
76
+ return { layer: provider.load(config, effectiveRoot), errors };
77
+ }
78
+ case 'cubejs': {
79
+ const provider = new CubejsProvider();
80
+ return { layer: provider.load(config, effectiveRoot), errors };
81
+ }
82
+ case 'snowflake': {
83
+ errors.push('Snowflake semantic views provider requires a live connection. Use the SnowflakeSemanticProvider directly.');
84
+ return { layer: undefined, errors };
85
+ }
86
+ case 'lookml': {
87
+ errors.push('LookML provider is not yet implemented. Use provider "dql", "dbt", or "cubejs".');
88
+ return { layer: undefined, errors };
89
+ }
90
+ default:
91
+ errors.push(`Unknown semantic layer provider: "${config.provider}". Supported: dql, dbt, cubejs, snowflake, lookml.`);
92
+ return { layer: undefined, errors };
42
93
  }
43
- case 'lookml': {
44
- // LookML provider not yet implemented
45
- return undefined;
46
- }
47
- default:
48
- return undefined;
94
+ }
95
+ catch (error) {
96
+ const msg = error instanceof Error ? error.message : String(error);
97
+ errors.push(`Failed to load ${config.provider} semantic layer: ${msg}`);
98
+ return { layer: undefined, errors };
49
99
  }
50
100
  }
51
101
  //# sourceMappingURL=registry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/semantic/providers/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAA+C,EAC/C,WAAmB;IAEnB,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,cAAc,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC7C,CAAC;IAED,6DAA6D;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;IACtD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC;QACnC,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,WAAW,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,cAAc,CACrB,MAAmC,EACnC,WAAmB;IAEnB,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC;YACnC,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC5C,CAAC;QACD,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC;YACnC,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC5C,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;YACtC,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC5C,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,sCAAsC;YACtC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/semantic/providers/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Repo resolver: clones or pulls a remote Git repository into a local cache
3
+ * directory so that file-based semantic layer providers (dbt, cubejs, etc.)
4
+ * can read definitions from it.
5
+ *
6
+ * Supports GitHub and GitLab repositories. Uses shallow clone with a single
7
+ * branch for speed. Caches clones under ~/.dql/cache/repos/ with a TTL so
8
+ * repeated runs don't re-clone every time.
9
+ */
10
+ import type { SemanticLayerProviderConfig } from './provider.js';
11
+ export interface RepoResolveResult {
12
+ /** Absolute path to the resolved project root (local dir or cloned repo + subPath). */
13
+ localPath: string;
14
+ /** Whether the repo was freshly cloned or updated. */
15
+ freshClone: boolean;
16
+ /** Any warnings (e.g., stale cache used). */
17
+ warnings: string[];
18
+ }
19
+ /**
20
+ * Resolve the effective local path for a semantic layer config.
21
+ *
22
+ * - If source is 'local' (default) or no repoUrl is set, returns the
23
+ * projectRoot + projectPath as-is.
24
+ * - If source is 'github' or 'gitlab', shallow-clones the repo into a
25
+ * cache directory and returns the path to the cloned content.
26
+ */
27
+ export declare function resolveRepoSource(config: SemanticLayerProviderConfig, projectRoot: string): RepoResolveResult;
28
+ /**
29
+ * Manually refresh a cached repo (for `dql semantic pull` CLI command).
30
+ */
31
+ export declare function pullCachedRepo(repoUrl: string, branch?: string): RepoResolveResult;
32
+ //# sourceMappingURL=repo-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-resolver.d.ts","sourceRoot":"","sources":["../../../src/semantic/providers/repo-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAwBjE,MAAM,WAAW,iBAAiB;IAChC,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,UAAU,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,2BAA2B,EACnC,WAAW,EAAE,MAAM,GAClB,iBAAiB,CAyEnB;AAwCD;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAe,GAAG,iBAAiB,CA0B1F"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Repo resolver: clones or pulls a remote Git repository into a local cache
3
+ * directory so that file-based semantic layer providers (dbt, cubejs, etc.)
4
+ * can read definitions from it.
5
+ *
6
+ * Supports GitHub and GitLab repositories. Uses shallow clone with a single
7
+ * branch for speed. Caches clones under ~/.dql/cache/repos/ with a TTL so
8
+ * repeated runs don't re-clone every time.
9
+ */
10
+ import { existsSync, mkdirSync, statSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { execSync } from 'node:child_process';
13
+ import { createHash } from 'node:crypto';
14
+ import { homedir } from 'node:os';
15
+ /** Default cache TTL: 10 minutes. */
16
+ const CACHE_TTL_MS = 10 * 60 * 1000;
17
+ /** Base directory for cached repo clones. */
18
+ function getCacheBaseDir() {
19
+ return join(homedir(), '.dql', 'cache', 'repos');
20
+ }
21
+ /**
22
+ * Compute a stable cache key from the repo URL + branch.
23
+ */
24
+ function cacheKey(repoUrl, branch) {
25
+ const hash = createHash('sha256').update(`${repoUrl}#${branch}`).digest('hex').slice(0, 16);
26
+ // Extract a human-readable slug from the URL
27
+ const slug = repoUrl
28
+ .replace(/^https?:\/\//, '')
29
+ .replace(/\.git$/, '')
30
+ .replace(/[^a-zA-Z0-9_-]/g, '_')
31
+ .slice(-60);
32
+ return `${slug}__${hash}`;
33
+ }
34
+ /**
35
+ * Resolve the effective local path for a semantic layer config.
36
+ *
37
+ * - If source is 'local' (default) or no repoUrl is set, returns the
38
+ * projectRoot + projectPath as-is.
39
+ * - If source is 'github' or 'gitlab', shallow-clones the repo into a
40
+ * cache directory and returns the path to the cloned content.
41
+ */
42
+ export function resolveRepoSource(config, projectRoot) {
43
+ const source = config.source ?? 'local';
44
+ if (source === 'local' || !config.repoUrl) {
45
+ // Local path — just resolve projectPath relative to projectRoot
46
+ const localPath = config.projectPath
47
+ ? join(projectRoot, config.projectPath)
48
+ : projectRoot;
49
+ return { localPath, freshClone: false, warnings: [] };
50
+ }
51
+ // Remote source: clone or update
52
+ const repoUrl = config.repoUrl;
53
+ const branch = config.branch ?? 'main';
54
+ const subPath = config.subPath ?? '';
55
+ const warnings = [];
56
+ const cacheBase = getCacheBaseDir();
57
+ mkdirSync(cacheBase, { recursive: true });
58
+ const key = cacheKey(repoUrl, branch);
59
+ const cloneDir = join(cacheBase, key);
60
+ let freshClone = false;
61
+ if (existsSync(cloneDir)) {
62
+ // Check if the cache is still fresh
63
+ const stat = statSync(cloneDir);
64
+ const age = Date.now() - stat.mtimeMs;
65
+ if (age < CACHE_TTL_MS) {
66
+ // Cache is fresh — use as-is
67
+ const localPath = subPath ? join(cloneDir, subPath) : cloneDir;
68
+ return { localPath, freshClone: false, warnings };
69
+ }
70
+ // Cache is stale — try to pull
71
+ try {
72
+ execSync(`git -C "${cloneDir}" fetch --depth=1 origin ${branch} && git -C "${cloneDir}" reset --hard origin/${branch}`, {
73
+ timeout: 30_000,
74
+ stdio: 'pipe',
75
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
76
+ });
77
+ freshClone = true;
78
+ }
79
+ catch (err) {
80
+ const msg = err instanceof Error ? err.message : String(err);
81
+ warnings.push(`Failed to update cached repo, using stale cache: ${msg}`);
82
+ }
83
+ }
84
+ else {
85
+ // Fresh clone
86
+ try {
87
+ const tokenEnv = getAuthEnv(repoUrl);
88
+ const authUrl = injectToken(repoUrl, tokenEnv);
89
+ execSync(`git clone --depth=1 --branch "${branch}" --single-branch "${authUrl}" "${cloneDir}"`, {
90
+ timeout: 60_000,
91
+ stdio: 'pipe',
92
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
93
+ });
94
+ freshClone = true;
95
+ }
96
+ catch (err) {
97
+ const msg = err instanceof Error ? err.message : String(err);
98
+ throw new Error(`Failed to clone semantic layer repo "${repoUrl}" (branch: ${branch}): ${msg}. ` +
99
+ 'Ensure the repo URL is correct and, for private repos, set GITHUB_TOKEN or GITLAB_TOKEN.');
100
+ }
101
+ }
102
+ const localPath = subPath ? join(cloneDir, subPath) : cloneDir;
103
+ return { localPath, freshClone, warnings };
104
+ }
105
+ /**
106
+ * Get the auth token from environment variables based on repo URL.
107
+ */
108
+ function getAuthEnv(repoUrl) {
109
+ if (repoUrl.includes('github.com') || repoUrl.includes('github')) {
110
+ return process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
111
+ }
112
+ if (repoUrl.includes('gitlab.com') || repoUrl.includes('gitlab')) {
113
+ return process.env.GITLAB_TOKEN ?? process.env.GL_TOKEN;
114
+ }
115
+ return undefined;
116
+ }
117
+ /**
118
+ * Inject an auth token into an HTTPS URL for git clone.
119
+ * Returns the original URL if no token is available or URL is not HTTPS.
120
+ */
121
+ function injectToken(repoUrl, token) {
122
+ if (!token)
123
+ return repoUrl;
124
+ try {
125
+ const url = new URL(repoUrl);
126
+ if (url.protocol !== 'https:')
127
+ return repoUrl;
128
+ if (repoUrl.includes('github')) {
129
+ // GitHub: https://x-access-token:TOKEN@github.com/owner/repo.git
130
+ url.username = 'x-access-token';
131
+ url.password = token;
132
+ }
133
+ else if (repoUrl.includes('gitlab')) {
134
+ // GitLab: https://oauth2:TOKEN@gitlab.com/owner/repo.git
135
+ url.username = 'oauth2';
136
+ url.password = token;
137
+ }
138
+ return url.toString();
139
+ }
140
+ catch {
141
+ return repoUrl;
142
+ }
143
+ }
144
+ /**
145
+ * Manually refresh a cached repo (for `dql semantic pull` CLI command).
146
+ */
147
+ export function pullCachedRepo(repoUrl, branch = 'main') {
148
+ const cacheBase = getCacheBaseDir();
149
+ const key = cacheKey(repoUrl, branch);
150
+ const cloneDir = join(cacheBase, key);
151
+ const warnings = [];
152
+ if (!existsSync(cloneDir)) {
153
+ // No cache — do a fresh clone
154
+ return resolveRepoSource({ provider: 'dbt', source: 'github', repoUrl, branch }, process.cwd());
155
+ }
156
+ try {
157
+ execSync(`git -C "${cloneDir}" fetch --depth=1 origin ${branch} && git -C "${cloneDir}" reset --hard origin/${branch}`, {
158
+ timeout: 30_000,
159
+ stdio: 'pipe',
160
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
161
+ });
162
+ }
163
+ catch (err) {
164
+ const msg = err instanceof Error ? err.message : String(err);
165
+ warnings.push(`Failed to pull: ${msg}`);
166
+ }
167
+ return { localPath: cloneDir, freshClone: true, warnings };
168
+ }
169
+ //# sourceMappingURL=repo-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-resolver.js","sourceRoot":"","sources":["../../../src/semantic/providers/repo-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Snowflake Semantic Views Provider.
3
+ *
4
+ * Introspects Snowflake's semantic views (CREATE SEMANTIC VIEW) to discover
5
+ * metrics, dimensions, and relationships. Unlike file-based providers (dbt,
6
+ * cubejs), this provider requires a live Snowflake connection.
7
+ *
8
+ * Because dql-core must not depend on dql-connectors, this provider accepts
9
+ * a generic query executor function. The CLI / local-runtime injects the
10
+ * actual Snowflake connector at runtime.
11
+ *
12
+ * Usage:
13
+ * const provider = new SnowflakeSemanticProvider(async (sql) => {
14
+ * return connector.execute(sql);
15
+ * });
16
+ * const layer = await provider.loadAsync(config, projectRoot);
17
+ */
18
+ import { SemanticLayer } from '../semantic-layer.js';
19
+ import type { SemanticLayerProviderConfig } from './provider.js';
20
+ export interface SnowflakeQueryRow {
21
+ [column: string]: unknown;
22
+ }
23
+ export interface SnowflakeQueryResult {
24
+ rows: SnowflakeQueryRow[];
25
+ }
26
+ /** A function that executes a SQL query against Snowflake and returns rows. */
27
+ export type SnowflakeQueryExecutor = (sql: string) => Promise<SnowflakeQueryResult>;
28
+ export declare class SnowflakeSemanticProvider {
29
+ private readonly executeQuery;
30
+ readonly name = "snowflake";
31
+ constructor(executeQuery: SnowflakeQueryExecutor);
32
+ /**
33
+ * Load semantic layer definitions from Snowflake semantic views.
34
+ * This is async because it needs to query Snowflake metadata.
35
+ */
36
+ loadAsync(config: SemanticLayerProviderConfig, _projectRoot: string): Promise<SemanticLayer>;
37
+ /**
38
+ * Discover available semantic views in the Snowflake account.
39
+ */
40
+ private discoverSemanticViews;
41
+ private discoverViaInformationSchema;
42
+ /**
43
+ * Load metrics from a semantic view's metadata.
44
+ */
45
+ private loadViewMetrics;
46
+ /**
47
+ * Load dimensions from a semantic view's metadata.
48
+ */
49
+ private loadViewDimensions;
50
+ /**
51
+ * Load relationships/joins from a semantic view's metadata.
52
+ */
53
+ private loadViewRelationships;
54
+ }
55
+ //# sourceMappingURL=snowflake-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snowflake-provider.d.ts","sourceRoot":"","sources":["../../../src/semantic/providers/snowflake-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAKjE,MAAM,WAAW,iBAAiB;IAChC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,iBAAiB,EAAE,CAAC;CAC3B;AAED,+EAA+E;AAC/E,MAAM,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEpF,qBAAa,yBAAyB;IAGxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAFzC,QAAQ,CAAC,IAAI,eAAe;gBAEC,YAAY,EAAE,sBAAsB;IAEjE;;;OAGG;IACG,SAAS,CACb,MAAM,EAAE,2BAA2B,EACnC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,aAAa,CAAC;IAiBzB;;OAEG;YACW,qBAAqB;YAsBrB,4BAA4B;IAuB1C;;OAEG;YACW,eAAe;IAgC7B;;OAEG;YACW,kBAAkB;IA6BhC;;OAEG;YACW,qBAAqB;CA4DpC"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Snowflake Semantic Views Provider.
3
+ *
4
+ * Introspects Snowflake's semantic views (CREATE SEMANTIC VIEW) to discover
5
+ * metrics, dimensions, and relationships. Unlike file-based providers (dbt,
6
+ * cubejs), this provider requires a live Snowflake connection.
7
+ *
8
+ * Because dql-core must not depend on dql-connectors, this provider accepts
9
+ * a generic query executor function. The CLI / local-runtime injects the
10
+ * actual Snowflake connector at runtime.
11
+ *
12
+ * Usage:
13
+ * const provider = new SnowflakeSemanticProvider(async (sql) => {
14
+ * return connector.execute(sql);
15
+ * });
16
+ * const layer = await provider.loadAsync(config, projectRoot);
17
+ */
18
+ import { SemanticLayer } from '../semantic-layer.js';
19
+ export class SnowflakeSemanticProvider {
20
+ executeQuery;
21
+ name = 'snowflake';
22
+ constructor(executeQuery) {
23
+ this.executeQuery = executeQuery;
24
+ }
25
+ /**
26
+ * Load semantic layer definitions from Snowflake semantic views.
27
+ * This is async because it needs to query Snowflake metadata.
28
+ */
29
+ async loadAsync(config, _projectRoot) {
30
+ const layer = new SemanticLayer();
31
+ const database = config.projectPath; // Reuse projectPath as database filter
32
+ // 1. Discover semantic views
33
+ const views = await this.discoverSemanticViews(database);
34
+ // 2. For each semantic view, load its metrics and dimensions
35
+ for (const view of views) {
36
+ await this.loadViewMetrics(layer, view);
37
+ await this.loadViewDimensions(layer, view);
38
+ await this.loadViewRelationships(layer, view);
39
+ }
40
+ return layer;
41
+ }
42
+ /**
43
+ * Discover available semantic views in the Snowflake account.
44
+ */
45
+ async discoverSemanticViews(database) {
46
+ // Snowflake exposes semantic views via SHOW SEMANTIC VIEWS or
47
+ // INFORMATION_SCHEMA. We try SHOW first as it's more universally available.
48
+ let sql = 'SHOW SEMANTIC VIEWS';
49
+ if (database) {
50
+ sql += ` IN DATABASE "${database}"`;
51
+ }
52
+ try {
53
+ const result = await this.executeQuery(sql);
54
+ return result.rows.map((row) => ({
55
+ name: String(row['name'] ?? row['NAME'] ?? ''),
56
+ databaseName: String(row['database_name'] ?? row['DATABASE_NAME'] ?? ''),
57
+ schemaName: String(row['schema_name'] ?? row['SCHEMA_NAME'] ?? ''),
58
+ }));
59
+ }
60
+ catch {
61
+ // SHOW SEMANTIC VIEWS may not be available in older Snowflake versions.
62
+ // Fall back to INFORMATION_SCHEMA query.
63
+ return this.discoverViaInformationSchema(database);
64
+ }
65
+ }
66
+ async discoverViaInformationSchema(database) {
67
+ const db = database ? `"${database}".` : '';
68
+ const sql = `
69
+ SELECT TABLE_NAME, TABLE_CATALOG, TABLE_SCHEMA
70
+ FROM ${db}INFORMATION_SCHEMA.TABLES
71
+ WHERE TABLE_TYPE = 'SEMANTIC VIEW'
72
+ ORDER BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME
73
+ `;
74
+ try {
75
+ const result = await this.executeQuery(sql);
76
+ return result.rows.map((row) => ({
77
+ name: String(row['TABLE_NAME'] ?? ''),
78
+ databaseName: String(row['TABLE_CATALOG'] ?? ''),
79
+ schemaName: String(row['TABLE_SCHEMA'] ?? ''),
80
+ }));
81
+ }
82
+ catch {
83
+ // If neither approach works, return empty — the error will surface as
84
+ // "no metrics found" which is more helpful than a raw SQL error.
85
+ return [];
86
+ }
87
+ }
88
+ /**
89
+ * Load metrics from a semantic view's metadata.
90
+ */
91
+ async loadViewMetrics(layer, view) {
92
+ const fqn = `"${view.databaseName}"."${view.schemaName}"."${view.name}"`;
93
+ try {
94
+ // DESC SEMANTIC VIEW returns columns with their semantic roles
95
+ const result = await this.executeQuery(`DESC SEMANTIC VIEW ${fqn}`);
96
+ for (const row of result.rows) {
97
+ const colName = String(row['name'] ?? row['NAME'] ?? '');
98
+ const colType = String(row['type'] ?? row['TYPE'] ?? '');
99
+ const semanticRole = String(row['semantic_role'] ?? row['SEMANTIC_ROLE'] ?? row['kind'] ?? row['KIND'] ?? '');
100
+ const expression = String(row['expression'] ?? row['EXPRESSION'] ?? row['default'] ?? row['DEFAULT'] ?? colName);
101
+ const description = String(row['comment'] ?? row['COMMENT'] ?? '');
102
+ if (semanticRole.toLowerCase() === 'measure' || semanticRole.toLowerCase() === 'metric') {
103
+ const aggregation = inferAggregation(colType, expression);
104
+ layer.addMetric({
105
+ name: `${view.name}.${colName}`,
106
+ label: colName.replace(/_/g, ' '),
107
+ description: description || `Metric from ${view.name}`,
108
+ sql: expression,
109
+ type: aggregation,
110
+ table: fqn,
111
+ domain: view.schemaName,
112
+ });
113
+ }
114
+ }
115
+ }
116
+ catch {
117
+ // Silently skip views we can't describe — they'll show as having no metrics
118
+ }
119
+ }
120
+ /**
121
+ * Load dimensions from a semantic view's metadata.
122
+ */
123
+ async loadViewDimensions(layer, view) {
124
+ const fqn = `"${view.databaseName}"."${view.schemaName}"."${view.name}"`;
125
+ try {
126
+ const result = await this.executeQuery(`DESC SEMANTIC VIEW ${fqn}`);
127
+ for (const row of result.rows) {
128
+ const colName = String(row['name'] ?? row['NAME'] ?? '');
129
+ const colType = String(row['type'] ?? row['TYPE'] ?? '');
130
+ const semanticRole = String(row['semantic_role'] ?? row['SEMANTIC_ROLE'] ?? row['kind'] ?? row['KIND'] ?? '');
131
+ const description = String(row['comment'] ?? row['COMMENT'] ?? '');
132
+ if (semanticRole.toLowerCase() === 'dimension' || semanticRole.toLowerCase() === 'entity') {
133
+ const dimType = inferDimensionType(colType);
134
+ layer.addDimension({
135
+ name: `${view.name}.${colName}`,
136
+ label: colName.replace(/_/g, ' '),
137
+ description: description || `Dimension from ${view.name}`,
138
+ sql: colName,
139
+ type: dimType,
140
+ table: fqn,
141
+ });
142
+ }
143
+ }
144
+ }
145
+ catch {
146
+ // Skip on error
147
+ }
148
+ }
149
+ /**
150
+ * Load relationships/joins from a semantic view's metadata.
151
+ */
152
+ async loadViewRelationships(layer, view) {
153
+ const fqn = `"${view.databaseName}"."${view.schemaName}"."${view.name}"`;
154
+ try {
155
+ // Try to get relationship metadata — this varies by Snowflake version
156
+ const result = await this.executeQuery(`SELECT * FROM TABLE(INFORMATION_SCHEMA.SEMANTIC_VIEW_RELATIONSHIPS('${fqn}'))`);
157
+ for (const row of result.rows) {
158
+ const leftTable = String(row['left_table'] ?? row['LEFT_TABLE'] ?? '');
159
+ const rightTable = String(row['right_table'] ?? row['RIGHT_TABLE'] ?? '');
160
+ const joinType = String(row['join_type'] ?? row['JOIN_TYPE'] ?? 'left');
161
+ const joinCondition = String(row['join_condition'] ?? row['JOIN_CONDITION'] ?? '');
162
+ if (leftTable && rightTable && joinCondition) {
163
+ // Register as a cube with join if both sides are available
164
+ const leftName = leftTable.split('.').pop() ?? leftTable;
165
+ const rightName = rightTable.split('.').pop() ?? rightTable;
166
+ const emptyCube = (cubeName, cubeTable) => ({
167
+ name: cubeName,
168
+ label: cubeName,
169
+ description: `Auto-discovered from Snowflake semantic view`,
170
+ sql: `SELECT * FROM ${cubeTable}`,
171
+ table: cubeTable,
172
+ domain: view.schemaName,
173
+ measures: [],
174
+ dimensions: [],
175
+ timeDimensions: [],
176
+ joins: [],
177
+ });
178
+ // Ensure both cubes exist
179
+ if (!layer.getCube(leftName)) {
180
+ layer.addCube(emptyCube(leftName, leftTable));
181
+ }
182
+ if (!layer.getCube(rightName)) {
183
+ layer.addCube(emptyCube(rightName, rightTable));
184
+ }
185
+ // Add join to the left cube
186
+ const leftCube = layer.getCube(leftName);
187
+ if (leftCube) {
188
+ const jt = joinType.toLowerCase();
189
+ const joinDef = {
190
+ name: `${leftName}_${rightName}`,
191
+ left: leftName,
192
+ right: rightName,
193
+ type: jt.includes('right') ? 'right' : jt.includes('full') ? 'full' : jt.includes('inner') ? 'inner' : 'left',
194
+ sql: joinCondition,
195
+ };
196
+ leftCube.joins.push(joinDef);
197
+ }
198
+ }
199
+ }
200
+ }
201
+ catch {
202
+ // Relationship introspection is optional — older Snowflake may not support it
203
+ }
204
+ }
205
+ }
206
+ /**
207
+ * Infer the metric aggregation type from the column type and expression.
208
+ */
209
+ function inferAggregation(colType, expression) {
210
+ const exprLower = expression.toLowerCase();
211
+ if (exprLower.includes('count('))
212
+ return 'count';
213
+ if (exprLower.includes('sum('))
214
+ return 'sum';
215
+ if (exprLower.includes('avg(') || exprLower.includes('average('))
216
+ return 'avg';
217
+ if (exprLower.includes('min('))
218
+ return 'min';
219
+ if (exprLower.includes('max('))
220
+ return 'max';
221
+ if (exprLower.includes('count_distinct(') || exprLower.includes('count(distinct'))
222
+ return 'count_distinct';
223
+ // Infer from column type
224
+ const typeLower = colType.toLowerCase();
225
+ if (typeLower.includes('number') || typeLower.includes('float') || typeLower.includes('decimal')) {
226
+ return 'sum';
227
+ }
228
+ return 'count';
229
+ }
230
+ /**
231
+ * Infer the dimension type from the column SQL type.
232
+ */
233
+ function inferDimensionType(colType) {
234
+ const typeLower = colType.toLowerCase();
235
+ if (typeLower.includes('date') || typeLower.includes('time') || typeLower.includes('timestamp')) {
236
+ return 'date';
237
+ }
238
+ if (typeLower.includes('number') || typeLower.includes('float') || typeLower.includes('decimal') || typeLower.includes('int')) {
239
+ return 'number';
240
+ }
241
+ if (typeLower.includes('bool')) {
242
+ return 'boolean';
243
+ }
244
+ return 'string';
245
+ }
246
+ //# sourceMappingURL=snowflake-provider.js.map