Skip to content

Commit 2c4d336

Browse files
committed
wip
1 parent 366b037 commit 2c4d336

File tree

11 files changed

+209
-20
lines changed

11 files changed

+209
-20
lines changed

.github/workflows/push.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
- 'packages/**'
1010
- 'rust/cubestore/**'
1111
- 'rust/cubesql/**'
12+
- 'rust/cubetranspilers/**'
1213
- '.eslintrc.js'
1314
- '.prettierrc'
1415
- 'package.json'
@@ -24,6 +25,7 @@ on:
2425
- 'packages/**'
2526
- 'rust/cubestore/**'
2627
- 'rust/cubesql/**'
28+
- 'rust/cubetranspilers/**'
2729
- '.eslintrc.js'
2830
- '.prettierrc'
2931
- 'package.json'
@@ -43,10 +45,15 @@ jobs:
4345
# Current docker version + next LTS
4446
node-version: [20.x, 22.x]
4547
transpile-worker-threads: [false, true]
48+
transpile-native: [false, true]
49+
exclude:
50+
- transpile-worker-threads: true
51+
transpile-native: true
4652
fail-fast: false
4753

4854
env:
4955
CUBEJS_TRANSPILATION_WORKER_THREADS: ${{ matrix.transpile-worker-threads }}
56+
CUBEJS_TRANSPILATION_NATIVE: ${{ matrix.transpile-native }}
5057
steps:
5158
- id: get-tag-out
5259
run: echo "$OUT"

packages/cubejs-backend-native/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ crate-type = ["cdylib", "lib"]
1818
[dependencies]
1919
cubesqlplanner = { path = "../../rust/cubesqlplanner/cubesqlplanner/" }
2020
cubenativeutils = { path = "../../rust/cubenativeutils" }
21+
cubetranspilers = { path = "../../rust/cubetranspilers" }
2122
async-channel = { version = "2" }
2223
async-trait = "0.1.36"
2324
convert_case = "0.6.0"
@@ -34,7 +35,10 @@ minijinja = { version = "1", features = ["json", "loader"] }
3435
once_cell = "1.10"
3536
# python
3637
pyo3 = { version = "0.20.0", features = [], optional = true }
37-
pyo3-asyncio = { version = "0.20.0", features = ["tokio-runtime", "attributes"], optional = true }
38+
pyo3-asyncio = { version = "0.20.0", features = [
39+
"tokio-runtime",
40+
"attributes",
41+
], optional = true }
3842
serde = { version = "1.0.209", features = ["derive"] }
3943
serde_json = "1.0.127"
4044
simple_logger = "1.7.0"

packages/cubejs-backend-native/js/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,22 @@ export type SQLInterfaceOptions = {
9898
gatewayPort?: number,
9999
};
100100

101+
export interface TransformConfig {
102+
fileName: string;
103+
transpilers: string[];
104+
metaData?: {
105+
cubeNames: string[];
106+
cubeSymbols: Record<string, Record<string, boolean>>;
107+
contextSymbols: Record<string, string>;
108+
}
109+
}
110+
111+
export interface TransformResponse {
112+
code: string;
113+
errors: string[];
114+
warnings: string[];
115+
}
116+
101117
export function loadNative() {
102118
// Development version
103119
if (fs.existsSync(path.join(__dirname, '/../../index.node'))) {
@@ -348,6 +364,12 @@ export const buildSqlAndParams = (cubeEvaluator: any): String => {
348364
return native.buildSqlAndParams(cubeEvaluator);
349365
};
350366

367+
export const transpileJs = async (content: String, metadata: TransformConfig): Promise<TransformResponse> => {
368+
const native = loadNative();
369+
370+
return native.transpileJs(content, metadata);
371+
};
372+
351373
export interface PyConfiguration {
352374
repositoryFactory?: (ctx: unknown) => Promise<unknown>,
353375
logger?: (msg: string, params: Record<string, any>) => void,

packages/cubejs-backend-native/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod node_obj_serializer;
1616
pub mod python;
1717
pub mod stream;
1818
pub mod template;
19+
pub mod transpilers;
1920
pub mod transport;
2021
pub mod utils;
2122

packages/cubejs-backend-native/src/node_export.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,9 @@ pub fn register_module_exports<C: NodeConfiguration + 'static>(
515515

516516
cx.export_function("buildSqlAndParams", build_sql_and_params)?;
517517

518+
//========= transpilers exports =================
519+
crate::transpilers::register_module(&mut cx)?;
520+
518521
crate::template::template_register_module(&mut cx)?;
519522

520523
#[cfg(feature = "python")]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
@@ -0,0 +1,91 @@
2+
use crate::node_obj_deserializer::JsValueDeserializer;
3+
use crate::node_obj_serializer::NodeObjSerializer;
4+
use anyhow::anyhow;
5+
use cubetranspilers::{run_transpilers, TransformConfig, Transpilers};
6+
use neon::context::{Context, FunctionContext, ModuleContext};
7+
use neon::prelude::{JsPromise, JsResult, JsValue, NeonResult};
8+
use neon::types::JsString;
9+
use serde::Deserialize;
10+
use std::collections::{HashMap, HashSet};
11+
use std::sync::RwLock;
12+
13+
#[derive(Deserialize, Default, Clone, Debug)]
14+
#[serde(rename_all = "camelCase")]
15+
pub struct TransformMetaData {
16+
pub cube_names: HashSet<String>,
17+
pub cube_symbols: HashMap<String, HashMap<String, bool>>,
18+
pub context_symbols: HashMap<String, String>,
19+
}
20+
21+
#[derive(Deserialize, Clone, Debug)]
22+
#[serde(rename_all = "camelCase")]
23+
pub struct TransformRequestConfig {
24+
pub file_name: String,
25+
pub transpilers: Vec<Transpilers>,
26+
pub meta_data: Option<TransformMetaData>,
27+
}
28+
29+
static METADATA_CACHE: RwLock<Option<TransformMetaData>> = RwLock::new(None);
30+
31+
pub fn register_module(cx: &mut ModuleContext) -> NeonResult<()> {
32+
cx.export_function("transpileJs", transpile_js)?;
33+
34+
Ok(())
35+
}
36+
37+
pub fn transpile_js(mut cx: FunctionContext) -> JsResult<JsPromise> {
38+
let content = cx.argument::<JsString>(0)?.value(&mut cx);
39+
let transform_data_js_object = cx.argument::<JsValue>(1)?;
40+
let deserializer = JsValueDeserializer::new(&mut cx, transform_data_js_object);
41+
let transform_request_config = TransformRequestConfig::deserialize(deserializer);
42+
43+
let promise = cx
44+
.task(move || {
45+
let transform_config: TransformConfig = match transform_request_config {
46+
Ok(data) => match data.meta_data {
47+
Some(meta_data) => {
48+
let mut config_lock = METADATA_CACHE.write().unwrap();
49+
let cache = TransformMetaData {
50+
cube_names: meta_data.cube_names,
51+
cube_symbols: meta_data.cube_symbols,
52+
context_symbols: meta_data.context_symbols,
53+
};
54+
let cfg = TransformConfig {
55+
file_name: data.file_name,
56+
transpilers: data.transpilers,
57+
cube_names: cache.cube_names.clone(),
58+
cube_symbols: cache.cube_symbols.clone(),
59+
context_symbols: cache.context_symbols.clone(),
60+
};
61+
*config_lock = Some(cache);
62+
cfg
63+
}
64+
None => {
65+
let cache = METADATA_CACHE.read().unwrap().clone().unwrap_or_default();
66+
TransformConfig {
67+
file_name: data.file_name,
68+
transpilers: data.transpilers,
69+
cube_names: cache.cube_names.clone(),
70+
cube_symbols: cache.cube_symbols.clone(),
71+
context_symbols: cache.context_symbols.clone(),
72+
}
73+
}
74+
},
75+
Err(err) => return Err(anyhow!("Failed to deserialize input data: {}", err)),
76+
};
77+
78+
run_transpilers(content, transform_config)
79+
})
80+
.promise(move |mut cx, res| match res {
81+
Ok(result) => {
82+
let obj = match NodeObjSerializer::serialize(&result, &mut cx) {
83+
Ok(data) => data,
84+
Err(err) => return cx.throw_error(err.to_string()),
85+
};
86+
Ok(obj)
87+
}
88+
Err(err) => cx.throw_error(err.to_string()),
89+
});
90+
91+
Ok(promise)
92+
}

packages/cubejs-backend-shared/src/env.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ const variables: Record<string, (...args: any) => any> = {
199199
transpilationWorkerThreadsCount: () => get('CUBEJS_TRANSPILATION_WORKER_THREADS_COUNT')
200200
.default('0')
201201
.asInt(),
202+
// This one takes precedence over CUBEJS_TRANSPILATION_WORKER_THREADS
203+
transpilationNative: () => get('CUBEJS_TRANSPILATION_NATIVE')
204+
.default('false')
205+
.asBoolStrict(),
202206

203207
/** ****************************************************************
204208
* Common db options *

packages/cubejs-schema-compiler/src/compiler/DataSchemaCompiler.js

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import R from 'ramda';
99
import workerpool from 'workerpool';
1010

1111
import { getEnv, isNativeSupported } from '@cubejs-backend/shared';
12+
import { transpileJs } from '@cubejs-backend/native';
1213
import { UserError } from './UserError';
1314
import { ErrorReporter } from './ErrorReporter';
15+
import { CONTEXT_SYMBOLS } from './CubeSymbols';
1416

1517
const NATIVE_IS_SUPPORTED = isNativeSupported();
1618

@@ -91,7 +93,10 @@ export class DataSchemaCompiler {
9193
const errorsReport = new ErrorReporter(null, [], this.errorReport);
9294
this.errorsReport = errorsReport;
9395

94-
if (getEnv('transpilationWorkerThreads')) {
96+
const transpilationWorkerThreads = getEnv('transpilationWorkerThreads');
97+
const transpilationNative = getEnv('transpilationNative');
98+
99+
if (!transpilationNative && transpilationWorkerThreads) {
95100
const wc = getEnv('transpilationWorkerThreadsCount');
96101
this.workerPool = workerpool.pool(
97102
path.join(__dirname, 'transpilers/transpiler_worker'),
@@ -101,26 +106,47 @@ export class DataSchemaCompiler {
101106

102107
const transpile = async () => {
103108
let cubeNames;
104-
let cubeSymbolsNames;
109+
let cubeSymbols;
110+
let transpilerNames;
111+
let results;
105112

106-
if (getEnv('transpilationWorkerThreads')) {
113+
if (transpilationNative || transpilationWorkerThreads) {
107114
cubeNames = Object.keys(this.cubeDictionary.byId);
108115
// We need only cubes and all its member names for transpiling.
109116
// Cubes doesn't change during transpiling, but are changed during compilation phase,
110117
// so we can prepare them once for every phase.
111118
// Communication between main and worker threads uses
112119
// The structured clone algorithm (@see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
113120
// which doesn't allow passing any function objects, so we need to sanitize the symbols.
114-
cubeSymbolsNames = Object.fromEntries(
121+
cubeSymbols = Object.fromEntries(
115122
Object.entries(this.cubeSymbols.symbols)
116123
.map(
117124
([key, value]) => [key, Object.fromEntries(
118125
Object.keys(value).map((k) => [k, true]),
119126
)],
120127
),
121128
);
129+
130+
// Transpilers are the same for all files within phase.
131+
transpilerNames = this.transpilers.map(t => t.constructor.name);
132+
}
133+
134+
if (transpilationNative) {
135+
// Warming up swc compiler cache
136+
const dummyFile = {
137+
fileName: 'dummy.js',
138+
content: ';',
139+
};
140+
141+
await this.transpileJsFile(dummyFile, errorsReport, { cubeNames, cubeSymbols, transpilerNames, contextSymbols: CONTEXT_SYMBOLS });
142+
143+
results = await Promise.all(toCompile.map(f => this.transpileFile(f, errorsReport, { transpilerNames })));
144+
} else if (transpilationWorkerThreads) {
145+
results = await Promise.all(toCompile.map(f => this.transpileFile(f, errorsReport, { cubeNames, cubeSymbols, transpilerNames })));
146+
} else {
147+
results = await Promise.all(toCompile.map(f => this.transpileFile(f, errorsReport, {})));
122148
}
123-
const results = await Promise.all(toCompile.map(f => this.transpileFile(f, errorsReport, { cubeNames, cubeSymbolsNames })));
149+
124150
return results.filter(f => !!f);
125151
};
126152

@@ -133,9 +159,19 @@ export class DataSchemaCompiler {
133159
contextCompilers: this.contextCompilers,
134160
}))
135161
.then(() => {
136-
if (this.workerPool) {
162+
if (transpilationNative) {
163+
// Clean up cache
164+
const dummyFile = {
165+
fileName: 'terminate.js',
166+
content: ';',
167+
};
168+
169+
return this.transpileJsFile(dummyFile, errorsReport, { cubeNames: [], cubeSymbols: {}, transpilerNames: [], contextSymbols: {} });
170+
} else if (transpilationWorkerThreads && this.workerPool) {
137171
this.workerPool.terminate();
138172
}
173+
174+
return Promise.resolve();
139175
});
140176
}
141177

@@ -177,15 +213,35 @@ export class DataSchemaCompiler {
177213
}
178214
}
179215

180-
async transpileJsFile(file, errorsReport, { cubeNames, cubeSymbolsNames }) {
216+
async transpileJsFile(file, errorsReport, { cubeNames, cubeSymbols, contextSymbols, transpilerNames }) {
181217
try {
182-
if (getEnv('transpilationWorkerThreads')) {
218+
if (getEnv('transpilationNative')) {
219+
const reqData = {
220+
fileName: file.fileName,
221+
transpilers: transpilerNames,
222+
...(cubeNames && {
223+
metaData: {
224+
cubeNames,
225+
cubeSymbols,
226+
contextSymbols,
227+
},
228+
}),
229+
};
230+
231+
errorsReport.inFile(file);
232+
const res = await transpileJs(file.content, reqData);
233+
errorsReport.addErrors(res.errors);
234+
errorsReport.addWarnings(res.warnings);
235+
errorsReport.exitFile();
236+
237+
return Object.assign({}, file, { content: res.code });
238+
} else if (getEnv('transpilationWorkerThreads')) {
183239
const data = {
184240
fileName: file.fileName,
185241
content: file.content,
186-
transpilers: this.transpilers.map(t => t.constructor.name),
242+
transpilers: transpilerNames,
187243
cubeNames,
188-
cubeSymbolsNames,
244+
cubeSymbols,
189245
};
190246

191247
const res = await this.workerPool.exec('transpile', [data]);
@@ -203,11 +259,11 @@ export class DataSchemaCompiler {
203259
},
204260
);
205261

262+
errorsReport.inFile(file);
206263
this.transpilers.forEach((t) => {
207-
errorsReport.inFile(file);
208264
babelTraverse(ast, t.traverseObject(errorsReport));
209-
errorsReport.exitFile();
210265
});
266+
errorsReport.exitFile();
211267

212268
const content = babelGenerator(ast, {}, file.content).code;
213269
return Object.assign({}, file, { content });

packages/cubejs-schema-compiler/src/compiler/ErrorReporter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class ErrorReporter {
108108
}
109109

110110
public error(e: any, fileName?: any, lineNumber?: any, position?: any) {
111-
const message = `${this.context.length ? `${this.context.join(' -> ')}: ` : ''}${e instanceof UserError ? e.message : (e.stack || e)}`;
111+
const message = `${this.context.length ? `${this.context.join(' -> ')}: ` : ''}${e.message ? e.message : (e.stack || e)}`;
112112
if (this.rootReporter().errors.find(m => (m.message || m) === message)) {
113113
return;
114114
}
@@ -142,15 +142,15 @@ export class ErrorReporter {
142142
}
143143

144144
public addErrors(errors: CompilerErrorInterface[]) {
145-
this.rootReporter().errors.push(...errors);
145+
errors.forEach((e: any) => { this.error(e); });
146146
}
147147

148148
public getWarnings() {
149149
return this.rootReporter().warnings;
150150
}
151151

152-
public addWarnings(warnings: SyntaxErrorInterface[]) {
153-
this.rootReporter().warnings.push(...warnings);
152+
public addWarnings(warnings: any[]) {
153+
warnings.forEach((w: any) => { this.warning(w); });
154154
}
155155

156156
protected rootReporter(): ErrorReporter {

0 commit comments

Comments
 (0)