monetdb 1.3.4 → 2.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/.github/workflows/Linux.yml +3 -3
- package/.github/workflows/docs.yml +79 -0
- package/.github/workflows/macos.yml +3 -3
- package/.github/workflows/monetdb-versions.yml +43 -0
- package/README.md +41 -511
- package/docs/components/alert.tsx +10 -0
- package/docs/components/info.tsx +6 -0
- package/docs/next.config.js +24 -0
- package/docs/package-lock.json +5069 -0
- package/docs/package.json +22 -0
- package/docs/pages/_app.js +9 -0
- package/docs/pages/_meta.json +16 -0
- package/docs/pages/apis/_meta.json +4 -0
- package/docs/pages/apis/connection.mdx +60 -0
- package/docs/pages/apis/result.mdx +39 -0
- package/docs/pages/index.mdx +27 -0
- package/docs/theme.config.js +35 -0
- package/docs/v1/README.md +532 -0
- package/package.json +16 -20
- package/src/PrepareStatement.ts +37 -0
- package/src/connection.ts +125 -0
- package/src/defaults.ts +13 -0
- package/src/file-transfer.ts +173 -0
- package/src/index.ts +3 -0
- package/src/mapi.ts +1016 -0
- package/src/monetize.ts +67 -0
- package/test/connection.ts +43 -0
- package/test/exec-queries.ts +100 -0
- package/test/filetransfer.ts +94 -0
- package/test/prepare-statement.ts +27 -0
- package/test/query-stream.ts +41 -0
- package/test/tmp/.gitignore +4 -0
- package/tsconfig.json +24 -0
- package/.travis.yml +0 -11
- package/dist/mapi.d.ts +0 -58
- package/dist/mapi.js +0 -250
- package/dist/mapi.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/foo.js +0 -16
- package/index.js +0 -5
- package/src/mapi-connection.js +0 -784
- package/src/monetdb-connection.js +0 -385
- package/src/utils.js +0 -27
- package/test/common.js +0 -45
- package/test/install-monetdb.sh +0 -11
- package/test/monetdb_stream.js +0 -106
- package/test/start-monetdb.sh +0 -38
- package/test/test.js +0 -908
- package/test/test_connection.js +0 -290
- /package/docs/{README.v0.md → v0/README.v0.md} +0 -0
- /package/docs/{MapiConnection.md → v1/MapiConnection.md} +0 -0
- /package/docs/{v1-notes.md → v1/v1-notes.md} +0 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
import { EventEmitter } from "events";
|
2
|
+
import {
|
3
|
+
MapiConfig,
|
4
|
+
MapiConnection,
|
5
|
+
parseMapiUri,
|
6
|
+
createMapiConfig,
|
7
|
+
HandShakeOption,
|
8
|
+
QueryResult,
|
9
|
+
QueryStream,
|
10
|
+
} from "./mapi";
|
11
|
+
import PrepareStatement from "./PrepareStatement";
|
12
|
+
|
13
|
+
// MAPI URI:
|
14
|
+
// tcp socket: mapi:monetdb://[<username>[:<password>]@]<host>[:<port>]/<database>
|
15
|
+
// unix domain socket: mapi:monetdb:///[<username>[:<password>]@]path/to/socket?database=<database>
|
16
|
+
type MAPI_URI = string;
|
17
|
+
|
18
|
+
type ConnectCallback = (err?: Error) => void;
|
19
|
+
|
20
|
+
class Connection extends EventEmitter {
|
21
|
+
autoCommit?: boolean;
|
22
|
+
replySize?: number;
|
23
|
+
sizeHeader?: boolean;
|
24
|
+
mapi: MapiConnection;
|
25
|
+
|
26
|
+
constructor(params: MapiConfig | MAPI_URI) {
|
27
|
+
super();
|
28
|
+
const config =
|
29
|
+
typeof params === "string"
|
30
|
+
? createMapiConfig(parseMapiUri(params))
|
31
|
+
: createMapiConfig(params);
|
32
|
+
this.mapi = new MapiConnection(config);
|
33
|
+
this.autoCommit = config.autoCommit;
|
34
|
+
this.replySize = config.replySize;
|
35
|
+
}
|
36
|
+
|
37
|
+
connect(callback?: ConnectCallback): Promise<boolean> {
|
38
|
+
const options = [
|
39
|
+
new HandShakeOption(
|
40
|
+
1,
|
41
|
+
"auto_commit",
|
42
|
+
this.autoCommit,
|
43
|
+
this.setAutocommit
|
44
|
+
),
|
45
|
+
new HandShakeOption(2, "reply_size", this.replySize, this.setReplySize),
|
46
|
+
new HandShakeOption(3, "size_header", true, this.setSizeHeader),
|
47
|
+
new HandShakeOption(
|
48
|
+
5,
|
49
|
+
"time_zone",
|
50
|
+
new Date().getTimezoneOffset() * 60,
|
51
|
+
this.setTimezone
|
52
|
+
),
|
53
|
+
];
|
54
|
+
const mapi = this.mapi;
|
55
|
+
return new Promise(async function (resolve, reject) {
|
56
|
+
try {
|
57
|
+
await mapi.connect(options);
|
58
|
+
resolve(mapi.ready());
|
59
|
+
if (callback) callback();
|
60
|
+
} catch (err) {
|
61
|
+
reject(err);
|
62
|
+
if (callback) callback(err);
|
63
|
+
}
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
close(): Promise<boolean> {
|
68
|
+
return this.mapi.disconnect();
|
69
|
+
}
|
70
|
+
|
71
|
+
commit(): Promise<void> {
|
72
|
+
return this.execute("COMMIT");
|
73
|
+
}
|
74
|
+
|
75
|
+
private command(str: string): Promise<any> {
|
76
|
+
return this.mapi.request(str);
|
77
|
+
}
|
78
|
+
|
79
|
+
execute(sql: string, stream: boolean = false): Promise<any> {
|
80
|
+
const query = `s${sql};\n`;
|
81
|
+
if (stream && this.replySize !== -1) this.setReplySize(-1);
|
82
|
+
return this.mapi.request(query, stream);
|
83
|
+
}
|
84
|
+
|
85
|
+
async prepare(sql: string): Promise<PrepareStatement> {
|
86
|
+
const prepSQL = `PREPARE ${sql}`;
|
87
|
+
const res = await this.execute(prepSQL);
|
88
|
+
return new PrepareStatement(res, this.mapi);
|
89
|
+
}
|
90
|
+
|
91
|
+
setAutocommit(v: boolean): Promise<boolean> {
|
92
|
+
const cmd = `Xauto_commit ${Number(v)}`;
|
93
|
+
return this.command(cmd).then(() => {
|
94
|
+
this.autoCommit = v;
|
95
|
+
return this.autoCommit;
|
96
|
+
});
|
97
|
+
}
|
98
|
+
|
99
|
+
setReplySize(v: number): Promise<number> {
|
100
|
+
const cmd = `Xreply_size ${Number(v)}`;
|
101
|
+
return this.command(cmd).then(() => {
|
102
|
+
this.replySize = Number(v);
|
103
|
+
return this.replySize;
|
104
|
+
});
|
105
|
+
}
|
106
|
+
|
107
|
+
setSizeHeader(v: boolean): Promise<boolean> {
|
108
|
+
const cmd = `Xsizeheader ${Number(v)}`;
|
109
|
+
return this.command(cmd).then(() => {
|
110
|
+
this.sizeHeader = v;
|
111
|
+
return this.sizeHeader;
|
112
|
+
});
|
113
|
+
}
|
114
|
+
|
115
|
+
setTimezone(sec: number): Promise<any> {
|
116
|
+
const qry = `SET TIME ZONE INTERVAL '${sec}' SECOND`;
|
117
|
+
return this.execute(qry);
|
118
|
+
}
|
119
|
+
|
120
|
+
rollback(): Promise<void> {
|
121
|
+
return this.execute("ROLLBACK");
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
export default Connection;
|
package/src/defaults.ts
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
const defaults = {
|
3
|
+
host: process.env.MAPI_HOST || 'localhost',
|
4
|
+
port: process.env.MAPI_PORT || 50000,
|
5
|
+
username: process.env.MAPI_USER || 'monetdb',
|
6
|
+
password: process.env.MAPI_PASSWORD || 'monetdb',
|
7
|
+
database: process.env.MAPI_DATABASE,
|
8
|
+
autoCommit: false,
|
9
|
+
replySize: 100,
|
10
|
+
};
|
11
|
+
|
12
|
+
export default defaults;
|
13
|
+
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
2
|
+
import * as path from 'node:path';
|
3
|
+
import { cwd } from 'node:process';
|
4
|
+
|
5
|
+
|
6
|
+
class FileHandler {
|
7
|
+
mapi: any;
|
8
|
+
file: string;
|
9
|
+
state: string;
|
10
|
+
err?: string;
|
11
|
+
eof?: boolean;
|
12
|
+
fhandle?: fs.FileHandle;
|
13
|
+
resolve?: (v?: any) => void;
|
14
|
+
reject?: (err?: Error) => void;
|
15
|
+
|
16
|
+
constructor(mapi: any, file: string) {
|
17
|
+
this.mapi = mapi;
|
18
|
+
this.file = file;
|
19
|
+
this.state = 'init';
|
20
|
+
}
|
21
|
+
|
22
|
+
async close(): Promise<void> {
|
23
|
+
if (this.fhandle) {
|
24
|
+
this.state = 'closed';
|
25
|
+
await this.fhandle.close();
|
26
|
+
this.fhandle = undefined;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
protected makePromise(): Promise<void> {
|
31
|
+
return new Promise((resolve, reject) => {
|
32
|
+
this.resolve = resolve;
|
33
|
+
this.reject = reject;
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
ready(): boolean {
|
38
|
+
return this.fhandle !== undefined
|
39
|
+
&& this.err === undefined
|
40
|
+
&& this.state === 'ready';
|
41
|
+
}
|
42
|
+
|
43
|
+
async initTransfer(flag: 'r'|'w'): Promise<void> {
|
44
|
+
if (this.fhandle === undefined) {
|
45
|
+
// for security reasons we do
|
46
|
+
// expect file to be relative to cwd
|
47
|
+
const fpath = path.join(cwd(), this.file);
|
48
|
+
if (fpath.startsWith(cwd())) {
|
49
|
+
try {
|
50
|
+
this.fhandle = await fs.open(fpath, flag);
|
51
|
+
} catch (err) {
|
52
|
+
await this.mapi.requestFileTransferError(`${err}\n`, this);
|
53
|
+
return this.makePromise();
|
54
|
+
}
|
55
|
+
// tell server we are okay with the download
|
56
|
+
// send magic new line
|
57
|
+
await this.mapi.requestFileTransfer(Buffer.from('\n'), this);
|
58
|
+
this.state = 'ready';
|
59
|
+
if (flag === 'r')
|
60
|
+
this.eof = false;
|
61
|
+
return this.makePromise();
|
62
|
+
} else {
|
63
|
+
// send err msg
|
64
|
+
await this.mapi.requestFileTransferError('Forbidden\n', this);
|
65
|
+
return this.makePromise();
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
class FileDownloader extends FileHandler {
|
74
|
+
bytesWritten: number;
|
75
|
+
|
76
|
+
constructor(mapi: any, file: string) {
|
77
|
+
super(mapi, file);
|
78
|
+
this.bytesWritten = 0;
|
79
|
+
}
|
80
|
+
|
81
|
+
async download(): Promise<void> {
|
82
|
+
if (this.state === 'init')
|
83
|
+
return this.initTransfer('w');
|
84
|
+
}
|
85
|
+
|
86
|
+
async writeChunk(data: Buffer): Promise<number> {
|
87
|
+
let bytes = 0;
|
88
|
+
if (this.ready()) {
|
89
|
+
try {
|
90
|
+
const { bytesWritten, buffer } = await this.fhandle.write(data);
|
91
|
+
bytes += bytesWritten;
|
92
|
+
} catch(err) {
|
93
|
+
this.err = err;
|
94
|
+
try {
|
95
|
+
await this.mapi.requestAbort();
|
96
|
+
} catch(err) {
|
97
|
+
// pass
|
98
|
+
console.error(err);
|
99
|
+
}
|
100
|
+
await this.close();
|
101
|
+
this.reject(err);
|
102
|
+
// kill connection
|
103
|
+
await this.mapi.disconnect();
|
104
|
+
throw err;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
this.bytesWritten += bytes;
|
108
|
+
return bytes;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
|
113
|
+
class FileUploader extends FileHandler {
|
114
|
+
skip: number;
|
115
|
+
bytesSent: number;
|
116
|
+
chunkSize: number;
|
117
|
+
eof: boolean;
|
118
|
+
|
119
|
+
constructor(mapi: any, file: string, skip: number = 0) {
|
120
|
+
super(mapi, file);
|
121
|
+
this.skip = skip > 0 ? skip - 1 : 0; // line based offset, super confusing
|
122
|
+
this.bytesSent = 0;
|
123
|
+
// configurable?
|
124
|
+
this.chunkSize = 1024 * 1024;
|
125
|
+
}
|
126
|
+
|
127
|
+
async upload(): Promise<void> {
|
128
|
+
if (this.state === 'init')
|
129
|
+
return this.initTransfer('r');
|
130
|
+
try {
|
131
|
+
await this.sendChunk();
|
132
|
+
} catch(err) {
|
133
|
+
this.err = err;
|
134
|
+
await this.mapi.requestAbort();
|
135
|
+
await this.close();
|
136
|
+
return this.reject(err);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
private async sendChunk(): Promise<void> {
|
141
|
+
let bytesRead: number = 0;
|
142
|
+
let buffer: Buffer = Buffer.alloc(0);
|
143
|
+
do {
|
144
|
+
const res = await this.fhandle.read(Buffer.alloc(this.chunkSize), 0, this.chunkSize);
|
145
|
+
bytesRead += res.bytesRead;
|
146
|
+
const data = Buffer.concat([buffer, res.buffer]).toString('utf8');
|
147
|
+
let offset: number = 0;
|
148
|
+
let eol = data.indexOf('\n');
|
149
|
+
while(this.skip && eol) {
|
150
|
+
offset = eol + 1;
|
151
|
+
this.skip--;
|
152
|
+
eol = data.indexOf('\n', offset);
|
153
|
+
}
|
154
|
+
buffer = Buffer.from(data).subarray(offset);
|
155
|
+
} while(this.skip && this.bytesSent === 0)
|
156
|
+
|
157
|
+
if (bytesRead > 0) {
|
158
|
+
// console.log(`read ${bytesRead} bytes`)
|
159
|
+
await this.mapi.requestFileTransfer(buffer.subarray(0, bytesRead), this);
|
160
|
+
this.bytesSent += bytesRead;
|
161
|
+
// console.log(`sent ${bytesRead} bytes`)
|
162
|
+
} else {
|
163
|
+
// reached EOF
|
164
|
+
this.eof = true;
|
165
|
+
// console.log(`reached eof`);
|
166
|
+
// send empty block to indicate end of upload
|
167
|
+
await this.mapi.requestFileTransfer(Buffer.from(''), this);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
export { FileUploader, FileDownloader };
|
173
|
+
|
package/src/index.ts
ADDED