Skip to content

Commit 082c2d1

Browse files
committed
Use packaged native core and remote demo dataset
1 parent caaa768 commit 082c2d1

9 files changed

Lines changed: 706 additions & 616 deletions

File tree

.gitmodules

Lines changed: 0 additions & 3 deletions
This file was deleted.

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,35 @@ For more information regarding launching and using Ladybug Explorer, please refe
233233
npm i
234234
```
235235

236-
#### Download and compile Ladybug
236+
#### Install Ladybug native bindings
237237

238-
```bash
239-
git submodule update --init --recursive
240-
npm run build-lbug
241-
```
238+
The `@ladybugdb/core` dependency installs the native module for your platform
239+
through its npm install script and optional platform packages.
242240

243241
#### Generate grammar files
244242

243+
The generated TypeScript parser is checked into `src/utils/CypherParser`, so
244+
you do not need to regenerate it for normal development. Regenerate it only
245+
when Ladybug's Cypher grammar changes.
246+
247+
To update the grammar:
248+
249+
1. Check out the Ladybug source tree at the version you want to match. For
250+
release updates, use the commit or tag that corresponds to the
251+
`@ladybugdb/core` version in `package.json`.
252+
2. Make the Ladybug checkout available at `./ladybug`. The generator currently
253+
reads `ladybug/scripts/antlr4/keywordhandler.py`,
254+
`ladybug/src/antlr4/Cypher.g4`, and `ladybug/src/antlr4/keywords.txt`.
255+
3. Run the generator:
256+
245257
```bash
246258
npm run generate-grammar
247259
```
248260

261+
4. Review and commit the regenerated files under `src/utils/CypherParser`.
262+
The temporary `ladybug` checkout is only an input to generation and should
263+
not be committed.
264+
249265
#### Fetch datasets
250266

251267
```bash

datasets.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,20 @@
77
"name": "Tutorial Dataset",
88
"path": "demo-db/csv",
99
"isProduction": true,
10-
"description": "Data used in Create Your First Graph (https://docs.ladybugdb.com/get-started/) tutorial."
10+
"description": "Data used in Create Your First Graph (https://docs.ladybugdb.com/get-started/) tutorial.",
11+
"source": {
12+
"type": "url",
13+
"baseUrl": "https://huggingface.co/datasets/ladybugdb/demo-db/resolve/main",
14+
"files": [
15+
"city.csv",
16+
"city.csv.gzip",
17+
"copy.cypher",
18+
"follows.csv",
19+
"lives-in.csv",
20+
"schema.cypher",
21+
"user.csv"
22+
]
23+
}
1124
},
1225
{
1326
"name": "TinySNB",

ladybug

Lines changed: 0 additions & 1 deletion
This file was deleted.

package-lock.json

Lines changed: 560 additions & 527 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
"serve-prod": "NODE_ENV=production LBUG_WASM=false node src/server/index.js",
1212
"build": "LBUG_IN_MEMORY=true LBUG_WASM=false NODE_ENV=production vue-cli-service build",
1313
"lint": "vue-cli-service lint",
14-
"build-lbug": "cd ladybug/tools/nodejs_api/ && npm i && cd ../.. && make nodejs NUM_THREADS=$(node -e \"console.log(require('os').cpus().length)\")",
1514
"fetch-datasets": "node src/server/utils/FetchDatasets.js",
1615
"generate-grammar-prod": "python3 ladybug/scripts/antlr4/keywordhandler.py ladybug/src/antlr4/Cypher.g4 ladybug/src/antlr4/keywords.txt ./Cypher.g4 /tmp/header.h && rm /tmp/header.h && rm -rf src/utils/CypherParser/ && npx antlr4ng-cli -o src/utils/CypherParser -Dlanguage=TypeScript -no-visitor -no-listener Cypher.g4 && rm Cypher.g4 && cd src/utils/CypherParser && mv CypherParser.ts CypherParserOld.ts && grep -v \"notify\" CypherParserOld.ts > CypherParser.ts && rm CypherParserOld.ts",
1716
"generate-grammar": "python3 ladybug/scripts/antlr4/keywordhandler.py ladybug/src/antlr4/Cypher.g4 ladybug/src/antlr4/keywords.txt ./Cypher.g4 /tmp/header.h && rm /tmp/header.h && rm -rf src/utils/CypherParser/ && npx antlr4ng-cli -o src/utils/CypherParser -Dlanguage=TypeScript -no-visitor -no-listener Cypher.g4 && rm Cypher.g4 && cd src/utils/CypherParser && mv CypherParser.ts CypherParserOld.ts && grep -v \"notify\" CypherParserOld.ts > CypherParser.ts && rm CypherParserOld.ts",
18-
"clean": "rm -rf node_modules dist datasets src/utils/CypherParser && git submodule deinit --all",
17+
"clean": "rm -rf node_modules dist datasets src/utils/CypherParser",
1918
"eslint": "eslint --ext .js,.vue src",
2019
"eslint-fix": "eslint --ext .js,.vue src --fix"
2120
},
@@ -37,10 +36,10 @@
3736
"devDependencies": {
3837
"@babel/core": "^7.12.16",
3938
"@babel/plugin-transform-private-methods": "^7.27.1",
40-
"@vue/cli-plugin-babel": "~5.0.0",
41-
"@vue/cli-plugin-eslint": "^5.0.8",
42-
"@vue/cli-plugin-typescript": "~5.0.0",
43-
"@vue/cli-service": "~5.0.0",
39+
"@vue/cli-plugin-babel": "5.0.9",
40+
"@vue/cli-plugin-eslint": "5.0.9",
41+
"@vue/cli-plugin-typescript": "5.0.9",
42+
"@vue/cli-service": "5.0.9",
4443
"antlr4ng-cli": "^1.0.0",
4544
"copy-webpack-plugin": "^12.0.2",
4645
"eslint": "^8.55.0",

src/server/Datasets.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ const getDatasetPath = (dataset) => {
4646
return path.resolve(path.join(base, datasetPath));
4747
};
4848

49+
const resolveCopyPath = (line, datasetPath) => {
50+
return line.replace(
51+
/(\bFROM\s+)(["'])(?!\/|[A-Za-z]:|https?:\/\/)([^"']+)\2/i,
52+
(_, prefix, quote, filePath) => {
53+
const resolvedPath = path.join(datasetPath, filePath).replaceAll("\\", "/");
54+
return `${prefix}${quote}${resolvedPath}${quote}`;
55+
}
56+
);
57+
};
58+
4959
router.get("/", async (_, res) => {
5060
await getDatasetsToShow();
5161
return res.send(Object.keys(DATASETS_TO_SHOW));
@@ -139,7 +149,7 @@ router.get("/:dataset/copy", async (req, res) => {
139149
.map((line) => line.trim())
140150
.filter((line) => line.length > 0)
141151
.map((line) => {
142-
return line.replace("dataset/", base + "/")
152+
return resolveCopyPath(line.replace("dataset/", base + "/"), datasetPath);
143153
});
144154
commands = ddls.concat(copyCommands);
145155
} catch (err) {

src/server/utils/Database.js

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,7 @@ const CONSTANTS = require("./Constants");
1010
const MODES = CONSTANTS.MODES;
1111
const READ_WRITE_MODE = MODES.READ_WRITE;
1212

13-
let lbug;
14-
if (process.env.NODE_ENV !== "production") {
15-
const lbugPath = path.join(
16-
__dirname,
17-
"..",
18-
"..",
19-
"..",
20-
"ladybug",
21-
"tools",
22-
"nodejs_api",
23-
"build/"
24-
);
25-
lbug = require(lbugPath);
26-
} else {
27-
lbug = require("@ladybugdb/core");
28-
}
13+
const lbug = require("@ladybugdb/core");
2914
const os = require("os");
3015

3116
class Database {

src/server/utils/FetchDatasets.js

Lines changed: 94 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { spawn } = require("child_process");
22
const path = require("path");
33
const fs = require("fs/promises");
4+
const https = require("https");
45

56
const tempPath = path.join(__dirname, "..", "..", "..", "temp");
67
const datasetsPath = path.join(__dirname, "..", "..", "..", "datasets");
@@ -16,22 +17,9 @@ const deleteFolderRecursive = async (path) => {
1617
}
1718
};
1819

19-
(async () => {
20-
console.log("Reading config file from", configPath);
21-
const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
22-
console.log("Deleting temp and datasets folders");
23-
await deleteFolderRecursive(tempPath);
24-
await deleteFolderRecursive(datasetsPath);
25-
console.log("Cloning repository");
26-
await new Promise((resolve, reject) => {
27-
const gitProcess = spawn("git", [
28-
"clone",
29-
"-n",
30-
"--depth=1",
31-
"--filter=tree:0",
32-
config.repository,
33-
tempPath,
34-
]);
20+
const runGit = (args, options = {}) => {
21+
return new Promise((resolve, reject) => {
22+
const gitProcess = spawn("git", args, options);
3523
gitProcess.stdout.on("data", (data) => {
3624
console.log(data.toString());
3725
});
@@ -42,57 +30,107 @@ const deleteFolderRecursive = async (path) => {
4230
if (code === 0) {
4331
resolve();
4432
} else {
45-
reject("Error cloning repository");
33+
reject(new Error(`Git command failed: git ${args.join(" ")}`));
4634
}
4735
});
4836
});
37+
};
38+
39+
const downloadFile = async (url, destination) => {
40+
await fs.mkdir(path.dirname(destination), { recursive: true });
41+
await new Promise((resolve, reject) => {
42+
https.get(url, (response) => {
43+
if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
44+
const nextUrl = new URL(response.headers.location, url).toString();
45+
response.resume();
46+
downloadFile(nextUrl, destination).then(resolve, reject);
47+
return;
48+
}
49+
if (response.statusCode !== 200) {
50+
response.resume();
51+
reject(new Error(`Failed to download ${url}: HTTP ${response.statusCode}`));
52+
return;
53+
}
54+
55+
const chunks = [];
56+
response.on("data", (chunk) => chunks.push(chunk));
57+
response.on("end", async () => {
58+
try {
59+
await fs.writeFile(destination, Buffer.concat(chunks));
60+
resolve();
61+
} catch (err) {
62+
reject(err);
63+
}
64+
});
65+
}).on("error", reject);
66+
});
67+
};
68+
69+
const fetchUrlDataset = async (dataset) => {
70+
const datasetPath = path.join(datasetsPath, dataset.path);
71+
const baseUrl = dataset.source.baseUrl.replace(/\/$/, "");
72+
console.log(`Downloading ${dataset.name} from ${baseUrl}`);
73+
for (const file of dataset.source.files) {
74+
const url = `${baseUrl}/${dataset.path}/${file}`;
75+
const destination = path.join(datasetPath, file);
76+
console.log(`\t - ${url}`);
77+
await downloadFile(url, destination);
78+
}
79+
};
80+
81+
const fetchGitDatasets = async (config, datasets) => {
82+
if (datasets.length === 0) {
83+
return;
84+
}
85+
86+
console.log("Cloning repository");
87+
await runGit([
88+
"clone",
89+
"-n",
90+
"--depth=1",
91+
"--filter=tree:0",
92+
config.repository,
93+
tempPath,
94+
]);
95+
4996
console.log("Gathering datasets paths");
50-
const datasetPaths = config.datasets.map((d) => {
97+
const datasetPaths = datasets.map((d) => {
5198
console.log(`\t - ${d.name} at ${d.path}`);
5299
return path.join(config.datasetsRoot, d.path);
53100
});
101+
54102
console.log("Initializing sparse checkout");
55-
await new Promise((resolve, reject) => {
56-
const gitProcess = spawn(
57-
"git",
58-
["sparse-checkout", "set", "--no-cone", ...datasetPaths],
59-
{ cwd: tempPath }
60-
);
61-
gitProcess.stdout.on("data", (data) => {
62-
console.log(data.toString());
63-
});
64-
gitProcess.stderr.on("data", (data) => {
65-
console.error(data.toString());
66-
});
67-
gitProcess.on("close", (code) => {
68-
if (code === 0) {
69-
resolve();
70-
} else {
71-
reject("Error initializing sparse checkout");
72-
}
73-
});
103+
await runGit(["sparse-checkout", "set", "--no-cone", ...datasetPaths], {
104+
cwd: tempPath,
74105
});
106+
75107
console.log("Pulling repository");
76-
await new Promise((resolve, reject) => {
77-
const gitProcess = spawn("git", ["checkout", config.commitHash], {
78-
cwd: tempPath,
79-
});
80-
gitProcess.stdout.on("data", (data) => {
81-
console.log(data.toString());
82-
});
83-
gitProcess.stderr.on("data", (data) => {
84-
console.error(data.toString());
85-
});
86-
gitProcess.on("close", (code) => {
87-
if (code === 0) {
88-
resolve();
89-
} else {
90-
reject("Error pulling repository");
91-
}
92-
});
93-
});
108+
await runGit(["checkout", config.commitHash], { cwd: tempPath });
109+
94110
console.log("Moving datasets");
95-
await fs.rename(path.join(tempPath, config.datasetsRoot), datasetsPath);
111+
for (const dataset of datasets) {
112+
const source = path.join(tempPath, config.datasetsRoot, dataset.path);
113+
const destination = path.join(datasetsPath, dataset.path);
114+
await fs.mkdir(path.dirname(destination), { recursive: true });
115+
await fs.rename(source, destination);
116+
}
117+
};
118+
119+
(async () => {
120+
console.log("Reading config file from", configPath);
121+
const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
122+
console.log("Deleting temp and datasets folders");
123+
await deleteFolderRecursive(tempPath);
124+
await deleteFolderRecursive(datasetsPath);
125+
126+
const urlDatasets = config.datasets.filter((d) => d.source?.type === "url");
127+
const gitDatasets = config.datasets.filter((d) => !d.source);
128+
129+
for (const dataset of urlDatasets) {
130+
await fetchUrlDataset(dataset);
131+
}
132+
await fetchGitDatasets(config, gitDatasets);
133+
96134
console.log("Deleting temp folder");
97135
await deleteFolderRecursive(tempPath);
98136
console.log("Done");

0 commit comments

Comments
 (0)