datafolder 1.0.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.
- package/README.md +169 -0
- package/index.js +3 -0
- package/package.json +27 -0
- package/sample.js +37 -0
- package/scripts/airbnb.js +73 -0
- package/scripts/airbnb2.js +74 -0
- package/scripts/airbnb2_queries.js +231 -0
- package/scripts/airbnb_queries.js +264 -0
- package/scripts/downloadFile.js +42 -0
- package/scripts/express.js +46 -0
- package/scripts/fastify.js +79 -0
- package/scripts/flights.js +75 -0
- package/scripts/flights2.js +81 -0
- package/scripts/flights2_queries.js +453 -0
- package/scripts/flights_queries.js +403 -0
- package/src/QyAcl.js +252 -0
- package/src/QyAclSaver.js +14 -0
- package/src/QyAclSaver_Worker.js +29 -0
- package/src/QyAclWriter.js +19 -0
- package/src/QyBatch.js +501 -0
- package/src/QyBinWriter.js +140 -0
- package/src/QyCD.js +61 -0
- package/src/QyCache.js +212 -0
- package/src/QyCompressor.js +60 -0
- package/src/QyCompressor_Worker.js +13 -0
- package/src/QyDB.js +122 -0
- package/src/QyDefaultOptions.js +59 -0
- package/src/QyDir.js +192 -0
- package/src/QyFetch.js +38 -0
- package/src/QyFile.js +124 -0
- package/src/QyFileContentUtils.js +175 -0
- package/src/QyFileLogger.js +58 -0
- package/src/QyIndex.js +237 -0
- package/src/QyIndexPropTree.js +92 -0
- package/src/QyIntersectNode.js +48 -0
- package/src/QyItem.js +105 -0
- package/src/QyJsonSaver.js +20 -0
- package/src/QyKVData.js +289 -0
- package/src/QyKVDataCleaner.js +17 -0
- package/src/QyKVDataCleaner_Worker.js +94 -0
- package/src/QyKVDataLoader.js +65 -0
- package/src/QyKVDataSaver.js +17 -0
- package/src/QyKVDataSaver_Worker.js +81 -0
- package/src/QyLogFileSaver.js +14 -0
- package/src/QyLogFileSaver_Worker.js +26 -0
- package/src/QyLogger.js +76 -0
- package/src/QyMessageWorker.js +41 -0
- package/src/QyMessager.js +84 -0
- package/src/QyMessager_Run.js +13 -0
- package/src/QyMod.js +15 -0
- package/src/QyOpRunner.js +73 -0
- package/src/QyQueue.js +60 -0
- package/src/QySaver.js +83 -0
- package/src/QySnapshotCompactor.js +17 -0
- package/src/QySnapshotCompactor_Worker.js +151 -0
- package/src/QySnapshotSaver.js +17 -0
- package/src/QySnapshotSaver_Worker.js +164 -0
- package/src/QySnapshots.js +354 -0
- package/src/QyStructure.js +50 -0
- package/src/QyTrigger.js +133 -0
- package/src/QyUnionNode.js +105 -0
- package/src/QyUtils.js +250 -0
- package/src/QyWorker.js +33 -0
- package/src/hackernews.js +93 -0
- package/src/rpc/evalCode.js +9 -0
- package/src/rpc/getFileContent.js +16 -0
- package/src/rpc/getSubItems.js +32 -0
- package/src/rpc/queryFiles.js +33 -0
- package/src/rpc/reloadRpc.js +1 -0
- package/src/simulate.js +114 -0
- package/src/simulate2.js +110 -0
- package/src/soc_pokec.js +43 -0
- package/static/bootstrap-5.3.8-dist/css/bootstrap.min.css +6 -0
- package/static/bootstrap-5.3.8-dist/js/bootstrap.bundle.js +6312 -0
- package/static/datafolderui/QyStyledElement.js +30 -0
- package/static/datafolderui/QyUIFolderTree.js +203 -0
- package/static/datafolderui/QyUIUtils.js +42 -0
- package/static/datafolderui/display-tree.js +108 -0
- package/static/datafolderui/explore-tree.js +122 -0
- package/static/datafolderui/folder-tree.js +5 -0
- package/static/datafolderui/hscrpt.js +25 -0
- package/static/datafolderui/index.css +6 -0
- package/static/datafolderui/index.html +20 -0
- package/static/datafolderui/json-view.js +74 -0
- package/static/datafolderui/kv-input.js +66 -0
- package/static/datafolderui/list-input.js +52 -0
- package/static/datafolderui/main-page.js +98 -0
- package/static/datafolderui/num-input.js +27 -0
- package/static/datafolderui/query-editor.js +229 -0
- package/static/datafolderui/querySelectorShadowRoot.js +24 -0
- package/static/datafolderui/resizable-input.js +67 -0
- package/static/favicon.ico +0 -0
- package/static/icons-1.13.1/font/bootstrap-icons.min.css +5 -0
- package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff +0 -0
- package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
- package/static/json-view/dist/jsonview.js +433 -0
- package/static/tree.js/tree.css +106 -0
- package/static/tree.js/tree.js +374 -0
- package/static/w2ui/dist/w2ui.min.css +2 -0
- package/static/w2ui/src/less/icons/icons.json +1 -0
- package/static/w2ui/src/less/icons/preview.html +33 -0
- package/static/w2ui/src/less/icons/readme.md +15 -0
- package/static/w2ui/src/less/icons/svg/box.svg +3 -0
- package/static/w2ui/src/less/icons/svg/check.svg +3 -0
- package/static/w2ui/src/less/icons/svg/colors.svg +3 -0
- package/static/w2ui/src/less/icons/svg/columns.svg +5 -0
- package/static/w2ui/src/less/icons/svg/cross.svg +4 -0
- package/static/w2ui/src/less/icons/svg/drop.svg +6 -0
- package/static/w2ui/src/less/icons/svg/empty.svg +3 -0
- package/static/w2ui/src/less/icons/svg/eye-dropper.svg +4 -0
- package/static/w2ui/src/less/icons/svg/info.svg +8 -0
- package/static/w2ui/src/less/icons/svg/paste.svg +4 -0
- package/static/w2ui/src/less/icons/svg/pencil.svg +5 -0
- package/static/w2ui/src/less/icons/svg/plus.svg +3 -0
- package/static/w2ui/src/less/icons/svg/reload.svg +5 -0
- package/static/w2ui/src/less/icons/svg/search.svg +3 -0
- package/static/w2ui/src/less/icons/svg/settings.svg +8 -0
- package/static/w2ui/src/less/icons/w2ui-font.css +36 -0
- package/static/w2ui/src/less/icons/w2ui-font.woff +0 -0
- package/static/w2ui/src/less/src/buttons.less +184 -0
- package/static/w2ui/src/less/src/common.less +541 -0
- package/static/w2ui/src/less/src/fields.less +302 -0
- package/static/w2ui/src/less/src/form.less +319 -0
- package/static/w2ui/src/less/src/grid.less +1057 -0
- package/static/w2ui/src/less/src/icons.less +37 -0
- package/static/w2ui/src/less/src/layout.less +86 -0
- package/static/w2ui/src/less/src/mixins.less +87 -0
- package/static/w2ui/src/less/src/popup.less +277 -0
- package/static/w2ui/src/less/src/sidebar.less +343 -0
- package/static/w2ui/src/less/src/tabs.less +145 -0
- package/static/w2ui/src/less/src/toolbar.less +219 -0
- package/static/w2ui/src/less/src/tooltip.less +948 -0
- package/static/w2ui/src/less/w2ui.less +196 -0
- package/static/w2ui/src/locale/az-az.json +114 -0
- package/static/w2ui/src/locale/ba-ba.json +114 -0
- package/static/w2ui/src/locale/bg-bg.json +114 -0
- package/static/w2ui/src/locale/ca-es.json +114 -0
- package/static/w2ui/src/locale/da-dk.json +114 -0
- package/static/w2ui/src/locale/de-de.json +114 -0
- package/static/w2ui/src/locale/en-gb.json +17 -0
- package/static/w2ui/src/locale/en-us.json +17 -0
- package/static/w2ui/src/locale/es-ar.json +115 -0
- package/static/w2ui/src/locale/es-cr.json +119 -0
- package/static/w2ui/src/locale/es-es.json +114 -0
- package/static/w2ui/src/locale/es-mx.json +114 -0
- package/static/w2ui/src/locale/fr-fr.json +114 -0
- package/static/w2ui/src/locale/gl-es.json +114 -0
- package/static/w2ui/src/locale/hr-hr.json +114 -0
- package/static/w2ui/src/locale/hu-hu.json +114 -0
- package/static/w2ui/src/locale/id-id.json +114 -0
- package/static/w2ui/src/locale/it-it.json +114 -0
- package/static/w2ui/src/locale/ja-jp.json +114 -0
- package/static/w2ui/src/locale/kk-kz.json +116 -0
- package/static/w2ui/src/locale/ko-kr.json +114 -0
- package/static/w2ui/src/locale/lt-lt.json +114 -0
- package/static/w2ui/src/locale/nl-nl.json +114 -0
- package/static/w2ui/src/locale/no-no.json +114 -0
- package/static/w2ui/src/locale/pl-pl.json +114 -0
- package/static/w2ui/src/locale/pt-br.json +114 -0
- package/static/w2ui/src/locale/ru-ru.json +116 -0
- package/static/w2ui/src/locale/sk-sk.json +114 -0
- package/static/w2ui/src/locale/sl-si.json +116 -0
- package/static/w2ui/src/locale/sr-rs.json +114 -0
- package/static/w2ui/src/locale/tr-tr.json +114 -0
- package/static/w2ui/src/locale/uk-ua.json +114 -0
- package/static/w2ui/src/locale/zh-cn.json +114 -0
- package/static/w2ui/src/locale/zh-tw.json +114 -0
- package/static/w2ui/src/query.js +583 -0
- package/static/w2ui/src/readme.md +11 -0
- package/static/w2ui/src/w2base.js +311 -0
- package/static/w2ui/src/w2compat.js +207 -0
- package/static/w2ui/src/w2field.js +1854 -0
- package/static/w2ui/src/w2form.js +2411 -0
- package/static/w2ui/src/w2grid.js +8916 -0
- package/static/w2ui/src/w2layout.js +1194 -0
- package/static/w2ui/src/w2locale.js +129 -0
- package/static/w2ui/src/w2popup.js +909 -0
- package/static/w2ui/src/w2sidebar.js +1769 -0
- package/static/w2ui/src/w2tabs.js +669 -0
- package/static/w2ui/src/w2toolbar.js +1180 -0
- package/static/w2ui/src/w2tooltip.js +3134 -0
- package/static/w2ui/src/w2utils.js +2457 -0
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# datafolder
|
|
2
|
+
|
|
3
|
+
datafolder is a highly efficient hierarchical virtual folder/file structured JSON database. It is implemented in pure native Node.js Javascript and has zero dependency, with full index and trigger features supported. The data can be organized into folders and sub folders, thus naturally partitioned. For example, airports, flights, orders can be stored as
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
airports/{country}/{state}/{airport_id} flights/{from}/{to}/{flight_id} orders/{year}/{month}/{day}/{order_id}
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
npm install "datafolder"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## How to use
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const DataFolder = require("datafolder");
|
|
19
|
+
const dataFolder = new DataFolder(); //Specify where to store the data. Default is ./data/sampleDataFolder
|
|
20
|
+
await dataFolder.start();
|
|
21
|
+
dataFolder.insert("users/2026/user_0001", {
|
|
22
|
+
userid: "user_0001",
|
|
23
|
+
email: "a@b.com",
|
|
24
|
+
score: 5,
|
|
25
|
+
address: { city: "a", zipcode: 123 },
|
|
26
|
+
});
|
|
27
|
+
dataFolder.insert("users/2026/user_0002", {
|
|
28
|
+
userid: "user_0002",
|
|
29
|
+
email: "c@b.com",
|
|
30
|
+
score: 3,
|
|
31
|
+
address: { city: "b", zipcode: 321 },
|
|
32
|
+
});
|
|
33
|
+
const { email, password, address, score } = dataFolder.view("users/2026/user_0002");
|
|
34
|
+
address.zipcode = 678;
|
|
35
|
+
dataFolder.insert("users/2026/user_0002", { score: score + 1, address });
|
|
36
|
+
await dataFolder.stop();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or operate with dir/file objects:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const file = dataFolder.file("users/2026/user_0002");
|
|
43
|
+
const { score, address } = file.view({ score: 1, address: 1 });
|
|
44
|
+
dataFolder.insert(file, { score: score + 1, address: { city: "c", zipcode: 663 } });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
### 1. Simple CRUD APIs
|
|
50
|
+
|
|
51
|
+
#### Create
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
dataFolder.createFile("path/to/file");
|
|
55
|
+
dataFolder.createDir("path/to/dir");
|
|
56
|
+
dataFolder.insert("path/to/file", { email, address: { city, zipcode } });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Read
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const dir = dataFolder.dir("path/to/dir");
|
|
63
|
+
const { subdirList, fileList, sortedSubdirList } = dir;
|
|
64
|
+
const file = dataFolder.file("path/to/file");
|
|
65
|
+
const { email, address } = dataFolder.view("path/to/file", { email: 1, addres: 1 });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Update
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
dataFolder.insert("path/to/file", { email: newEmail, address: { zipcode: newZipcode } });
|
|
72
|
+
dataFolder.insert(file, { score: score + 1 });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Delete
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
dataFolder.delDir("path/to/dir");
|
|
79
|
+
dataFolder.delFile("path/to/file");
|
|
80
|
+
dataFolder.delContent("path/to/file");
|
|
81
|
+
dataFolder.remove("path/to/file", { email: 1, address: { zipcode: 1 } });
|
|
82
|
+
dataFolder.remove(file, { email: 1, address: { zipcode: 1 } });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Batch APIs
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
const [dir, file, { email, score }] = dataFolder.fetch
|
|
89
|
+
.dir("path/to/dir")
|
|
90
|
+
.file("path/to/file")
|
|
91
|
+
.view("path/to/file")
|
|
92
|
+
.run();
|
|
93
|
+
dataFolder.batch
|
|
94
|
+
.insert("path/to/file", { email: "email1@a.b.com" })
|
|
95
|
+
.insert("path/to/file", { email: "email2", score: 100 })
|
|
96
|
+
.remove("path/to/file", { address: { city: 1 } })
|
|
97
|
+
.run();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. Powerful Query Support. Auto Create Needed Indexes
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
dataFolder.queryFiles("flights/**", { _from: "airports/LAX" });
|
|
104
|
+
dataFolder.queryFiles(["flights", "*", 1, (x) => x >= 5 && x <= 7], {
|
|
105
|
+
$or: [{ _from: "airports/BIS" }, { _to: "airports/BIS" }],
|
|
106
|
+
});
|
|
107
|
+
dataFolder.queryFilesMulti([
|
|
108
|
+
[["airbnb", "Loft", "Entire home/apt", "Real Bed"], { address: { street: "Porto, Porto, Portugal" } }],
|
|
109
|
+
["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
|
|
110
|
+
]);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 4. Flexible Trigger Support to Monitor Any Changes
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
function onEmailChanged({ studentId, email }) {
|
|
117
|
+
const { oldValue, newValue } = email;
|
|
118
|
+
// Logic to handle email change.
|
|
119
|
+
}
|
|
120
|
+
dataFolder.insertTrigger("emailChanged", "students/{studentId}", "email");
|
|
121
|
+
dataFolder.on("emailChanged", onEmailChanged);
|
|
122
|
+
dataFolder.insert("students/student1", { email: "email1" });
|
|
123
|
+
dataFolder.insert("students/student2", { email: "email2" });
|
|
124
|
+
dataFolder.insert("students/student1", { email: "email1_changed" });
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Will trigger the following calls:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
onEmailChanged({ studentId: "student1", email: { oldValue: undefined, newValue: "email1" } });
|
|
131
|
+
onEmailChanged({ studentId: "student2", email: { oldValue: undefined, newValue: "email2" } });
|
|
132
|
+
onEmailChanged({ studentId: "student1", email: { oldValue: "email1", newValue: "email1_changed" } });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Example
|
|
136
|
+
|
|
137
|
+
Once you install datafolder via
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm insall datafolder
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
you can cd to node_modules/datafolder, then run
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
to install the dev dependency fastify. Then run
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm run airbnb
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
It will download the [AirBnB data](https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/listingsAndReviews.json) (around 100MB) and insert into the following folder structure:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
airbnb/{property_type}/{room_type}/{bed_type}/{_id}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Once the data is inserted, it will open the browser at [datafolderui](http://localhost:3628/datafolderui/index.html) and the whole folder structures can be navigated.
|
|
162
|
+
|
|
163
|
+
Next time to bring up the UI:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npm run ui
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Complex query examples can be found in the scripts folder source code.
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "datafolder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "lysh <1wopjdzn1@mozmail.com>",
|
|
5
|
+
"description": "datafolder is a highly efficient hierarchical virtual folder/file structured JSON database. It is implemented in pure native Node.js Javascript and has zero dependency, with full index and trigger features supported.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"Hierarchical JSON Database",
|
|
9
|
+
"virtual folder/file structured",
|
|
10
|
+
"LSM Based Persistency",
|
|
11
|
+
"Pure Javascript",
|
|
12
|
+
"Zero Dependency",
|
|
13
|
+
"Highly Efficient"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"fastify": "^5.0.0",
|
|
18
|
+
"@fastify/compress": "^8.1.0",
|
|
19
|
+
"@fastify/express": "^4.0.2",
|
|
20
|
+
"@fastify/static": "^8.3.0"
|
|
21
|
+
},
|
|
22
|
+
"exports": "./index.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"ui": "start http://localhost:3628/datafolderui/index.html && node ./scripts/fastify.js",
|
|
25
|
+
"airbnb": "node ./scripts/airbnb2.js && start http://localhost:3628/datafolderui/index.html && node ./scripts/fastify.js"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/sample.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const DataFolder = require("datafolder");
|
|
2
|
+
const dataFolder = new DataFolder();
|
|
3
|
+
|
|
4
|
+
run();
|
|
5
|
+
|
|
6
|
+
async function run() {
|
|
7
|
+
await dataFolder.start();
|
|
8
|
+
dataFolder.insert("users/2026/user_0001", {
|
|
9
|
+
userid: "user_0001",
|
|
10
|
+
email: "a@b.com",
|
|
11
|
+
password: "123",
|
|
12
|
+
score: 5,
|
|
13
|
+
address: { city: "a", zipcode: 123 },
|
|
14
|
+
});
|
|
15
|
+
dataFolder.insert("users/2026/user_0002", {
|
|
16
|
+
userid: "user_0002",
|
|
17
|
+
email: "c@b.com",
|
|
18
|
+
password: "333",
|
|
19
|
+
score: 3,
|
|
20
|
+
address: { city: "b", zipcode: 321 },
|
|
21
|
+
});
|
|
22
|
+
const { email, password, address, score } = dataFolder.view("users/2026/user_0002");
|
|
23
|
+
logger.log(email, password, score, address);
|
|
24
|
+
address.zipcode = 678;
|
|
25
|
+
dataFolder.insert("users/2026/user_0002", { score: score + 1, address });
|
|
26
|
+
logger.log(dataFolder.view("users/2026/user_0002", ["score", "address"]));
|
|
27
|
+
await dataFolder.stop();
|
|
28
|
+
await dataFolder.start();
|
|
29
|
+
{
|
|
30
|
+
const file = dataFolder.file("users/2026/user_0002");
|
|
31
|
+
const { email, password, score } = file.view();
|
|
32
|
+
logger.log(email, password, score);
|
|
33
|
+
dataFolder.insert(file, { score: score + 1 });
|
|
34
|
+
logger.log(file.view());
|
|
35
|
+
}
|
|
36
|
+
await dataFolder.stop();
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const fsPromises = require("node:fs/promises");
|
|
3
|
+
const { QyDB } = require("../src/QyDB.js");
|
|
4
|
+
const { folderOrFileExists, isSimpleType } = require("../src/QyUtils.js");
|
|
5
|
+
const { runQueries } = require("./airbnb_queries.js");
|
|
6
|
+
const { downloadFile } = require("./downloadFile.js");
|
|
7
|
+
|
|
8
|
+
const fileName = "listingsAndReviews.json";
|
|
9
|
+
const fileUrl = `https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/${fileName}`;
|
|
10
|
+
const airbnbJsonFilePath = path.join(__dirname, "../data/", fileName);
|
|
11
|
+
const qyDB = new QyDB();
|
|
12
|
+
const { rootFolder } = qyDB;
|
|
13
|
+
|
|
14
|
+
run();
|
|
15
|
+
async function insertData() {
|
|
16
|
+
console.time("insertData");
|
|
17
|
+
const { batch } = qyDB;
|
|
18
|
+
const fileHandle = await fsPromises.open(airbnbJsonFilePath, "r");
|
|
19
|
+
for await (const line of fileHandle.readLines()) {
|
|
20
|
+
const oneItem = convert(JSON.parse(line));
|
|
21
|
+
batch.insert(["airbnb1", oneItem._id], oneItem);
|
|
22
|
+
}
|
|
23
|
+
batch.run();
|
|
24
|
+
console.timeEnd("insertData");
|
|
25
|
+
}
|
|
26
|
+
async function run() {
|
|
27
|
+
try {
|
|
28
|
+
logger.log(`Downloading ${fileName}`);
|
|
29
|
+
await downloadFile(fileUrl, airbnbJsonFilePath);
|
|
30
|
+
console.log("done");
|
|
31
|
+
} catch (exception) {
|
|
32
|
+
logger.error(`Downloading ${fileUrl} failed:`, exception);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.time("total");
|
|
36
|
+
if (process.argv[3]) {
|
|
37
|
+
logger.level = process.argv[3];
|
|
38
|
+
}
|
|
39
|
+
let shouldInsertData = true;
|
|
40
|
+
if (process.argv[2] == "true") {
|
|
41
|
+
try {
|
|
42
|
+
await fsPromises.rm(rootFolder, { recursive: true });
|
|
43
|
+
} catch (exception) {}
|
|
44
|
+
shouldInsertData = true;
|
|
45
|
+
} else if (!(await folderOrFileExists(rootFolder))) shouldInsertData = true;
|
|
46
|
+
if (shouldInsertData) {
|
|
47
|
+
console.time("run");
|
|
48
|
+
await qyDB.start();
|
|
49
|
+
await insertData();
|
|
50
|
+
await qyDB.stop();
|
|
51
|
+
console.timeEnd("run");
|
|
52
|
+
}
|
|
53
|
+
console.time("queryWithStartEnd");
|
|
54
|
+
console.time("start");
|
|
55
|
+
await qyDB.start();
|
|
56
|
+
console.timeEnd("start");
|
|
57
|
+
runQueries(qyDB);
|
|
58
|
+
await qyDB.stop();
|
|
59
|
+
console.timeEnd("queryWithStartEnd");
|
|
60
|
+
console.timeEnd("total");
|
|
61
|
+
}
|
|
62
|
+
function convert(item) {
|
|
63
|
+
if (isSimpleType(item)) return item;
|
|
64
|
+
const { $date } = item;
|
|
65
|
+
if ($date != undefined) return new Date(+$date.$numberLong);
|
|
66
|
+
const number = item.$numberInt || item.$numberLong || item.$numberDecimal || item.$numberDouble;
|
|
67
|
+
if (number != undefined) return +number;
|
|
68
|
+
for (const prop in item) {
|
|
69
|
+
item[prop] = convert(item[prop]);
|
|
70
|
+
}
|
|
71
|
+
return item;
|
|
72
|
+
}
|
|
73
|
+
Object.assign(module.exports, { runQueries });
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const fsPromises = require("node:fs/promises");
|
|
3
|
+
const { QyDB } = require("../src/QyDB.js");
|
|
4
|
+
const { folderOrFileExists, isSimpleType } = require("../src/QyUtils.js");
|
|
5
|
+
const { runQueries } = require("./airbnb2_queries.js");
|
|
6
|
+
const { downloadFile } = require("./downloadFile.js");
|
|
7
|
+
|
|
8
|
+
const fileName = "listingsAndReviews.json";
|
|
9
|
+
const fileUrl = `https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/${fileName}`;
|
|
10
|
+
const airbnbJsonFilePath = path.join(__dirname, "../data/", fileName);
|
|
11
|
+
const qyDB = new QyDB();
|
|
12
|
+
const { rootFolder } = qyDB;
|
|
13
|
+
|
|
14
|
+
run();
|
|
15
|
+
async function insertData() {
|
|
16
|
+
console.time("insertData");
|
|
17
|
+
const { batch } = qyDB;
|
|
18
|
+
const fileHandle = await fsPromises.open(airbnbJsonFilePath, "r");
|
|
19
|
+
for await (const line of fileHandle.readLines()) {
|
|
20
|
+
const oneItem = convert(JSON.parse(line));
|
|
21
|
+
const { property_type, room_type, bed_type, _id } = oneItem;
|
|
22
|
+
batch.insert(["airbnb", property_type, room_type, bed_type, _id], oneItem);
|
|
23
|
+
}
|
|
24
|
+
batch.run();
|
|
25
|
+
console.timeEnd("insertData");
|
|
26
|
+
}
|
|
27
|
+
async function run() {
|
|
28
|
+
try {
|
|
29
|
+
logger.log(`Downloading ${fileName}`);
|
|
30
|
+
await downloadFile(fileUrl, airbnbJsonFilePath);
|
|
31
|
+
console.log("done");
|
|
32
|
+
} catch (exception) {
|
|
33
|
+
logger.error(`Downloading ${fileUrl} failed:`, exception);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.time("total");
|
|
37
|
+
if (process.argv[3]) {
|
|
38
|
+
logger.level = process.argv[3];
|
|
39
|
+
}
|
|
40
|
+
let shouldInsertData = true;
|
|
41
|
+
if (process.argv[2] == "true") {
|
|
42
|
+
try {
|
|
43
|
+
await fsPromises.rm(rootFolder, { recursive: true });
|
|
44
|
+
} catch (exception) {}
|
|
45
|
+
shouldInsertData = true;
|
|
46
|
+
} else if (!(await folderOrFileExists(rootFolder))) shouldInsertData = true;
|
|
47
|
+
if (shouldInsertData) {
|
|
48
|
+
console.time("run");
|
|
49
|
+
await qyDB.start();
|
|
50
|
+
await insertData();
|
|
51
|
+
await qyDB.stop();
|
|
52
|
+
console.timeEnd("run");
|
|
53
|
+
}
|
|
54
|
+
console.time("queryWithStartEnd");
|
|
55
|
+
console.time("start");
|
|
56
|
+
await qyDB.start();
|
|
57
|
+
console.timeEnd("start");
|
|
58
|
+
runQueries(qyDB);
|
|
59
|
+
await qyDB.stop();
|
|
60
|
+
console.timeEnd("queryWithStartEnd");
|
|
61
|
+
console.timeEnd("total");
|
|
62
|
+
}
|
|
63
|
+
function convert(item) {
|
|
64
|
+
if (isSimpleType(item)) return item;
|
|
65
|
+
const { $date } = item;
|
|
66
|
+
if ($date != undefined) return new Date(+$date.$numberLong);
|
|
67
|
+
const number = item.$numberInt || item.$numberLong || item.$numberDecimal || item.$numberDouble;
|
|
68
|
+
if (number != undefined) return +number;
|
|
69
|
+
for (const prop in item) {
|
|
70
|
+
item[prop] = convert(item[prop]);
|
|
71
|
+
}
|
|
72
|
+
return item;
|
|
73
|
+
}
|
|
74
|
+
Object.assign(module.exports, { runQueries });
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
|
|
3
|
+
function query1(qyDB) {
|
|
4
|
+
console.time("query1");
|
|
5
|
+
assert.deepEqual(
|
|
6
|
+
qyDB
|
|
7
|
+
.queryFiles(["airbnb", "Loft", "Entire home/apt", "Real Bed"], {
|
|
8
|
+
address: { street: "Porto, Porto, Portugal" },
|
|
9
|
+
})
|
|
10
|
+
.map((x) => x.view(["name"]).name),
|
|
11
|
+
[
|
|
12
|
+
"Porto city centre apartment H",
|
|
13
|
+
"Romantic loft near historic center",
|
|
14
|
+
"Lovely sun light in a stylish loft, Downtown Spot",
|
|
15
|
+
"Charming & Sunny Mezzanine Loft",
|
|
16
|
+
"Be Happy in Porto",
|
|
17
|
+
"Quarry Apartment",
|
|
18
|
+
"Loft with balcony and view of Casa da Música PORTO",
|
|
19
|
+
"For 4 or 6 - Wonderful Apartment downtown",
|
|
20
|
+
"Clérigos Apartments - Apt. 4E for 2/3 Pax",
|
|
21
|
+
"Bright Top Floor Two Rooms Apartment w/ Terrace",
|
|
22
|
+
"ReUrban Flats - Santa Catarina #3",
|
|
23
|
+
"The Blue and White Porto House - Vintage",
|
|
24
|
+
"4twoLoft at Great Location",
|
|
25
|
+
"Step In Porto - Loft w/Mezzanine",
|
|
26
|
+
"Your OWM Invicta",
|
|
27
|
+
"Pury Apartments",
|
|
28
|
+
"Live Porto & Douro - Breiner III",
|
|
29
|
+
],
|
|
30
|
+
);
|
|
31
|
+
console.timeEnd("query1");
|
|
32
|
+
}
|
|
33
|
+
function query2(qyDB) {
|
|
34
|
+
console.time("query2");
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
qyDB
|
|
37
|
+
.queryFiles(
|
|
38
|
+
["airbnb", "Loft", "Entire home/apt", "Real Bed"],
|
|
39
|
+
{
|
|
40
|
+
address: { street: "Porto, Porto, Portugal" },
|
|
41
|
+
},
|
|
42
|
+
9,
|
|
43
|
+
)
|
|
44
|
+
.map((x) => x.view(["name"]).name),
|
|
45
|
+
[
|
|
46
|
+
"Porto city centre apartment H",
|
|
47
|
+
"Romantic loft near historic center",
|
|
48
|
+
"Lovely sun light in a stylish loft, Downtown Spot",
|
|
49
|
+
"Charming & Sunny Mezzanine Loft",
|
|
50
|
+
"Be Happy in Porto",
|
|
51
|
+
"Quarry Apartment",
|
|
52
|
+
"Loft with balcony and view of Casa da Música PORTO",
|
|
53
|
+
"For 4 or 6 - Wonderful Apartment downtown",
|
|
54
|
+
"Clérigos Apartments - Apt. 4E for 2/3 Pax",
|
|
55
|
+
],
|
|
56
|
+
);
|
|
57
|
+
console.timeEnd("query2");
|
|
58
|
+
}
|
|
59
|
+
function query3(qyDB) {
|
|
60
|
+
console.time("query3");
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
qyDB
|
|
63
|
+
.queryFiles(
|
|
64
|
+
["airbnb", "Loft", "Entire home/apt", "Real Bed"],
|
|
65
|
+
{ address: { street: "Porto, Porto, Portugal" } },
|
|
66
|
+
9,
|
|
67
|
+
9,
|
|
68
|
+
)
|
|
69
|
+
.map((x) => x.view(["name"]).name),
|
|
70
|
+
[
|
|
71
|
+
"Bright Top Floor Two Rooms Apartment w/ Terrace",
|
|
72
|
+
"ReUrban Flats - Santa Catarina #3",
|
|
73
|
+
"The Blue and White Porto House - Vintage",
|
|
74
|
+
"4twoLoft at Great Location",
|
|
75
|
+
"Step In Porto - Loft w/Mezzanine",
|
|
76
|
+
"Your OWM Invicta",
|
|
77
|
+
"Pury Apartments",
|
|
78
|
+
"Live Porto & Douro - Breiner III",
|
|
79
|
+
],
|
|
80
|
+
);
|
|
81
|
+
console.timeEnd("query3");
|
|
82
|
+
}
|
|
83
|
+
function query4(qyDB) {
|
|
84
|
+
console.time("query4");
|
|
85
|
+
assert.deepEqual(
|
|
86
|
+
qyDB.fetch
|
|
87
|
+
.queryFilesMulti([
|
|
88
|
+
[["airbnb", "Loft", "Entire home/apt", "Real Bed"], { address: { street: "Porto, Porto, Portugal" } }],
|
|
89
|
+
["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
|
|
90
|
+
])
|
|
91
|
+
.run()[0]
|
|
92
|
+
.map((x) => x.view(["name"]).name)
|
|
93
|
+
.sort(),
|
|
94
|
+
[
|
|
95
|
+
"Porto city centre apartment H",
|
|
96
|
+
"Romantic loft near historic center",
|
|
97
|
+
"Lovely sun light in a stylish loft, Downtown Spot",
|
|
98
|
+
"Charming & Sunny Mezzanine Loft",
|
|
99
|
+
"Brand New Luxury Downtown Studio",
|
|
100
|
+
"Be Happy in Porto",
|
|
101
|
+
"Quarry Apartment",
|
|
102
|
+
"Loft with balcony and view of Casa da Música PORTO",
|
|
103
|
+
"For 4 or 6 - Wonderful Apartment downtown",
|
|
104
|
+
"Clérigos Apartments - Apt. 4E for 2/3 Pax",
|
|
105
|
+
"Bright Top Floor Two Rooms Apartment w/ Terrace",
|
|
106
|
+
"ReUrban Flats - Santa Catarina #3",
|
|
107
|
+
"The Blue and White Porto House - Vintage",
|
|
108
|
+
"4twoLoft at Great Location",
|
|
109
|
+
"Step In Porto - Loft w/Mezzanine",
|
|
110
|
+
"Your OWM Invicta",
|
|
111
|
+
"Pury Apartments",
|
|
112
|
+
"Live Porto & Douro - Breiner III",
|
|
113
|
+
].sort(),
|
|
114
|
+
);
|
|
115
|
+
console.timeEnd("query4");
|
|
116
|
+
}
|
|
117
|
+
function query5(qyDB) {
|
|
118
|
+
console.time("query5");
|
|
119
|
+
assert.deepEqual(
|
|
120
|
+
qyDB
|
|
121
|
+
.queryFilesMulti(
|
|
122
|
+
[
|
|
123
|
+
[
|
|
124
|
+
["airbnb", "Loft", "Entire home/apt", "Real Bed"],
|
|
125
|
+
{ address: { street: "Porto, Porto, Portugal" } },
|
|
126
|
+
],
|
|
127
|
+
["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
|
|
128
|
+
],
|
|
129
|
+
3,
|
|
130
|
+
)
|
|
131
|
+
.map((x) => x.view(["name"]).name),
|
|
132
|
+
[
|
|
133
|
+
"Porto city centre apartment H",
|
|
134
|
+
"Romantic loft near historic center",
|
|
135
|
+
"Lovely sun light in a stylish loft, Downtown Spot",
|
|
136
|
+
],
|
|
137
|
+
);
|
|
138
|
+
console.timeEnd("query5");
|
|
139
|
+
}
|
|
140
|
+
function query6(qyDB) {
|
|
141
|
+
console.time("query6");
|
|
142
|
+
assert.deepEqual(
|
|
143
|
+
qyDB
|
|
144
|
+
.queryFilesMulti(
|
|
145
|
+
[
|
|
146
|
+
[
|
|
147
|
+
["airbnb", "Loft", "Entire home/apt", "Real Bed"],
|
|
148
|
+
{ address: { street: "Porto, Porto, Portugal" } },
|
|
149
|
+
],
|
|
150
|
+
["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
|
|
151
|
+
],
|
|
152
|
+
20,
|
|
153
|
+
10,
|
|
154
|
+
)
|
|
155
|
+
.map((x) => x.view(["name"]).name),
|
|
156
|
+
[
|
|
157
|
+
"ReUrban Flats - Santa Catarina #3",
|
|
158
|
+
"The Blue and White Porto House - Vintage",
|
|
159
|
+
"4twoLoft at Great Location",
|
|
160
|
+
"Step In Porto - Loft w/Mezzanine",
|
|
161
|
+
"Your OWM Invicta",
|
|
162
|
+
"Pury Apartments",
|
|
163
|
+
"Live Porto & Douro - Breiner III",
|
|
164
|
+
"Brand New Luxury Downtown Studio",
|
|
165
|
+
],
|
|
166
|
+
);
|
|
167
|
+
console.timeEnd("query6");
|
|
168
|
+
}
|
|
169
|
+
function query7(qyDB) {
|
|
170
|
+
console.time("query7");
|
|
171
|
+
assert.deepEqual(
|
|
172
|
+
qyDB.queryFiles(["airbnb", "Loft", "**"], { minimum_nights: (x) => x >= 60 }).map((x) => x.view(["name"]).name),
|
|
173
|
+
["Romantic loft near historic center", "Brand New Luxury Downtown Studio"],
|
|
174
|
+
);
|
|
175
|
+
console.timeEnd("query7");
|
|
176
|
+
}
|
|
177
|
+
function query8(qyDB) {
|
|
178
|
+
console.time("query8");
|
|
179
|
+
assert.deepEqual(
|
|
180
|
+
qyDB
|
|
181
|
+
.queryFiles(["airbnb", "Loft", ""], { minimum_nights: (x) => x >= 60 }, 10, 10)
|
|
182
|
+
.map((x) => x.view(["name"]).name),
|
|
183
|
+
[],
|
|
184
|
+
);
|
|
185
|
+
console.timeEnd("query8");
|
|
186
|
+
}
|
|
187
|
+
function query9(qyDB) {
|
|
188
|
+
console.time("query9");
|
|
189
|
+
assert.deepEqual(
|
|
190
|
+
qyDB
|
|
191
|
+
.queryFilesMulti(
|
|
192
|
+
[
|
|
193
|
+
[
|
|
194
|
+
["airbnb", "Loft", "Entire home/apt", "Real Bed"],
|
|
195
|
+
{ address: { street: "Porto, Porto, Portugal" } },
|
|
196
|
+
],
|
|
197
|
+
["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
|
|
198
|
+
],
|
|
199
|
+
3,
|
|
200
|
+
)
|
|
201
|
+
.map((x) => x.view(["name"]).name),
|
|
202
|
+
[
|
|
203
|
+
"Porto city centre apartment H",
|
|
204
|
+
"Romantic loft near historic center",
|
|
205
|
+
"Lovely sun light in a stylish loft, Downtown Spot",
|
|
206
|
+
],
|
|
207
|
+
);
|
|
208
|
+
console.timeEnd("query9");
|
|
209
|
+
}
|
|
210
|
+
function runQueries(qyDB) {
|
|
211
|
+
query1(qyDB);
|
|
212
|
+
query1(qyDB);
|
|
213
|
+
query2(qyDB);
|
|
214
|
+
query2(qyDB);
|
|
215
|
+
query3(qyDB);
|
|
216
|
+
query3(qyDB);
|
|
217
|
+
query4(qyDB);
|
|
218
|
+
query4(qyDB);
|
|
219
|
+
query5(qyDB);
|
|
220
|
+
query5(qyDB);
|
|
221
|
+
query6(qyDB);
|
|
222
|
+
query6(qyDB);
|
|
223
|
+
query7(qyDB);
|
|
224
|
+
query7(qyDB);
|
|
225
|
+
query8(qyDB);
|
|
226
|
+
query8(qyDB);
|
|
227
|
+
query9(qyDB);
|
|
228
|
+
query9(qyDB);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Object.assign(module.exports, { runQueries });
|