@sqlrooms/duckdb 0.0.2 → 0.1.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/dist/__tests__/arrow-utils.test.d.ts +2 -0
- package/dist/__tests__/arrow-utils.test.d.ts.map +1 -0
- package/dist/__tests__/arrow-utils.test.js +186 -0
- package/dist/__tests__/sql-from.test.d.ts +2 -0
- package/dist/__tests__/sql-from.test.d.ts.map +1 -0
- package/dist/__tests__/sql-from.test.js +70 -0
- package/dist/arrow-utils.d.ts +8 -0
- package/dist/arrow-utils.d.ts.map +1 -0
- package/dist/arrow-utils.js +22 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +17 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrow-utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/arrow-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as arrow from 'apache-arrow';
|
|
2
|
+
import { arrowTableToJson } from '../arrow-utils';
|
|
3
|
+
/// TODO: Revise tests for nested objects once https://github.com/apache/arrow/issues/33394 is fixed
|
|
4
|
+
describe('arrow-utils', () => {
|
|
5
|
+
describe('arrowTableToJson', () => {
|
|
6
|
+
it('should convert Arrow table to JSON array', () => {
|
|
7
|
+
// Create a simple Arrow table
|
|
8
|
+
const data = [
|
|
9
|
+
{ id: 1n, name: 'John', score: 95.5 },
|
|
10
|
+
{ id: 2n, name: 'Jane', score: 88.0 },
|
|
11
|
+
];
|
|
12
|
+
const table = arrow.tableFromJSON(data);
|
|
13
|
+
const result = arrowTableToJson(table);
|
|
14
|
+
const expected = [
|
|
15
|
+
{ id: 1, name: 'John', score: 95.5 },
|
|
16
|
+
{ id: 2, name: 'Jane', score: 88.0 },
|
|
17
|
+
];
|
|
18
|
+
expect(result).toEqual(expected);
|
|
19
|
+
});
|
|
20
|
+
it('should handle large BigInt values', () => {
|
|
21
|
+
const largeNumber = 9007199254740991n; // Number.MAX_SAFE_INTEGER
|
|
22
|
+
const data = [
|
|
23
|
+
{ id: largeNumber, value: 'test' },
|
|
24
|
+
{ id: largeNumber + 1n, value: 'test2' },
|
|
25
|
+
];
|
|
26
|
+
const table = arrow.tableFromJSON(data);
|
|
27
|
+
const result = arrowTableToJson(table);
|
|
28
|
+
const expected = [
|
|
29
|
+
{ id: 9007199254740991, value: 'test' },
|
|
30
|
+
{ id: '9007199254740992', value: 'test2' }, // Beyond MAX_SAFE_INTEGER, should be string
|
|
31
|
+
];
|
|
32
|
+
expect(result).toEqual(expected);
|
|
33
|
+
});
|
|
34
|
+
it('should handle timestamps and dates', () => {
|
|
35
|
+
const timestamp = new Date('2024-02-05T12:30:45.123Z');
|
|
36
|
+
const date = new Date('2024-02-05');
|
|
37
|
+
const data = [
|
|
38
|
+
{
|
|
39
|
+
id: 1,
|
|
40
|
+
timestamp: timestamp,
|
|
41
|
+
date: date,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const table = arrow.tableFromJSON(data);
|
|
45
|
+
const result = arrowTableToJson(table);
|
|
46
|
+
expect(result).toEqual([
|
|
47
|
+
{
|
|
48
|
+
id: 1,
|
|
49
|
+
timestamp: timestamp.getTime(),
|
|
50
|
+
date: date.getTime(),
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
it('should handle nested objects (structs)', () => {
|
|
55
|
+
/// TODO: Revise tests for nested objects once https://github.com/apache/arrow/issues/33394 is fixed
|
|
56
|
+
// For now, we'll use JSON strings for nested objects as Arrow's JS API
|
|
57
|
+
// doesn't seem to have a straightforward way to create struct types
|
|
58
|
+
const data = [
|
|
59
|
+
{
|
|
60
|
+
id: 1,
|
|
61
|
+
user: JSON.stringify({
|
|
62
|
+
name: 'John',
|
|
63
|
+
address: {
|
|
64
|
+
city: 'New York',
|
|
65
|
+
country: 'USA',
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 2,
|
|
71
|
+
user: JSON.stringify({
|
|
72
|
+
name: 'Jane',
|
|
73
|
+
address: {
|
|
74
|
+
city: 'London',
|
|
75
|
+
country: 'UK',
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
const table = arrow.tableFromJSON(data);
|
|
81
|
+
const result = arrowTableToJson(table);
|
|
82
|
+
// Parse the JSON strings back to objects for comparison
|
|
83
|
+
const parsedResult = result.map((row) => ({
|
|
84
|
+
...row,
|
|
85
|
+
user: JSON.parse(row.user),
|
|
86
|
+
}));
|
|
87
|
+
expect(parsedResult).toEqual([
|
|
88
|
+
{
|
|
89
|
+
id: 1,
|
|
90
|
+
user: {
|
|
91
|
+
name: 'John',
|
|
92
|
+
address: {
|
|
93
|
+
city: 'New York',
|
|
94
|
+
country: 'USA',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 2,
|
|
100
|
+
user: {
|
|
101
|
+
name: 'Jane',
|
|
102
|
+
address: {
|
|
103
|
+
city: 'London',
|
|
104
|
+
country: 'UK',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
it('should handle arrays', () => {
|
|
111
|
+
// For now, we'll use JSON strings for arrays as Arrow's JS API
|
|
112
|
+
// doesn't seem to have a straightforward way to create list types
|
|
113
|
+
const data = [
|
|
114
|
+
{
|
|
115
|
+
id: 1,
|
|
116
|
+
tags: JSON.stringify(['a', 'b']),
|
|
117
|
+
scores: JSON.stringify([1, 2, 3]),
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
const table = arrow.tableFromJSON(data);
|
|
121
|
+
const result = arrowTableToJson(table);
|
|
122
|
+
// Parse the JSON strings back to arrays for comparison
|
|
123
|
+
const parsedResult = result.map((row) => ({
|
|
124
|
+
...row,
|
|
125
|
+
tags: JSON.parse(row.tags),
|
|
126
|
+
scores: JSON.parse(row.scores),
|
|
127
|
+
}));
|
|
128
|
+
expect(parsedResult).toEqual([
|
|
129
|
+
{
|
|
130
|
+
id: 1,
|
|
131
|
+
tags: ['a', 'b'],
|
|
132
|
+
scores: [1, 2, 3],
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
it('should handle combination of structs, BigInts, and timestamps', () => {
|
|
137
|
+
const timestamp = new Date('2024-02-05T12:30:45.123Z');
|
|
138
|
+
// const largeNumber = 9007199254740991n; // MAX_SAFE_INTEGER
|
|
139
|
+
const largeNumber = 1;
|
|
140
|
+
// Create data with JSON strings for complex structures
|
|
141
|
+
const data = [
|
|
142
|
+
{
|
|
143
|
+
id: largeNumber,
|
|
144
|
+
created_at: timestamp,
|
|
145
|
+
metadata: JSON.stringify({
|
|
146
|
+
user: {
|
|
147
|
+
id: largeNumber, // + 1n,
|
|
148
|
+
name: 'John',
|
|
149
|
+
last_login: new Date('2024-01-01T00:00:00Z').getTime(),
|
|
150
|
+
stats: {
|
|
151
|
+
score: 95.5,
|
|
152
|
+
rank: largeNumber, // + 2n,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
tags: ['active', 'premium'],
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
const table = arrow.tableFromJSON(data);
|
|
160
|
+
const result = arrowTableToJson(table);
|
|
161
|
+
// Parse the JSON strings back to objects for comparison
|
|
162
|
+
const parsedResult = result.map((row) => ({
|
|
163
|
+
...row,
|
|
164
|
+
metadata: JSON.parse(row.metadata),
|
|
165
|
+
}));
|
|
166
|
+
expect(parsedResult).toEqual([
|
|
167
|
+
{
|
|
168
|
+
id: 1,
|
|
169
|
+
created_at: timestamp.getTime(),
|
|
170
|
+
metadata: {
|
|
171
|
+
user: {
|
|
172
|
+
id: 1,
|
|
173
|
+
name: 'John',
|
|
174
|
+
last_login: new Date('2024-01-01T00:00:00Z').getTime(),
|
|
175
|
+
stats: {
|
|
176
|
+
score: 95.5,
|
|
177
|
+
rank: 1,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
tags: ['active', 'premium'],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
]);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-from.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sql-from.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { sqlFrom, literalToSQL } from '../sql-from';
|
|
2
|
+
describe('sql-from', () => {
|
|
3
|
+
describe('literalToSQL', () => {
|
|
4
|
+
it('should convert numbers correctly', () => {
|
|
5
|
+
expect(literalToSQL(42)).toBe('42');
|
|
6
|
+
expect(literalToSQL(3.14)).toBe('3.14');
|
|
7
|
+
expect(literalToSQL(Infinity)).toBe('NULL');
|
|
8
|
+
expect(literalToSQL(NaN)).toBe('NULL');
|
|
9
|
+
});
|
|
10
|
+
it('should convert strings correctly', () => {
|
|
11
|
+
expect(literalToSQL('hello')).toBe("'hello'");
|
|
12
|
+
expect(literalToSQL("O'Neil")).toBe("'O''Neil'"); // Escapes single quotes
|
|
13
|
+
});
|
|
14
|
+
it('should convert booleans correctly', () => {
|
|
15
|
+
expect(literalToSQL(true)).toBe('TRUE');
|
|
16
|
+
expect(literalToSQL(false)).toBe('FALSE');
|
|
17
|
+
});
|
|
18
|
+
it('should convert dates correctly', () => {
|
|
19
|
+
const date = new Date('2024-02-05');
|
|
20
|
+
expect(literalToSQL(date)).toMatch(/^DATE '\d{4}-\d{1,2}-\d{1,2}'$/);
|
|
21
|
+
const timestamp = new Date('2024-02-05T12:30:00Z');
|
|
22
|
+
expect(literalToSQL(timestamp)).toMatch(/^epoch_ms\(\d+\)$/);
|
|
23
|
+
});
|
|
24
|
+
it('should handle null and undefined', () => {
|
|
25
|
+
expect(literalToSQL(null)).toBe('NULL');
|
|
26
|
+
expect(literalToSQL(undefined)).toBe('NULL');
|
|
27
|
+
});
|
|
28
|
+
it('should convert RegExp to string', () => {
|
|
29
|
+
expect(literalToSQL(/test/)).toBe("'test'");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('sqlFrom', () => {
|
|
33
|
+
it('should create SQL query from array of objects', () => {
|
|
34
|
+
const data = [
|
|
35
|
+
{ id: 1, name: 'John' },
|
|
36
|
+
{ id: 2, name: 'Jane' },
|
|
37
|
+
];
|
|
38
|
+
const result = sqlFrom(data);
|
|
39
|
+
expect(result).toBe('(SELECT 1 AS "id", \'John\' AS "name") ' +
|
|
40
|
+
'UNION ALL ' +
|
|
41
|
+
'(SELECT 2 AS "id", \'Jane\' AS "name")');
|
|
42
|
+
});
|
|
43
|
+
it('should handle custom column mapping', () => {
|
|
44
|
+
const data = [
|
|
45
|
+
{ id: 1, firstName: 'John' },
|
|
46
|
+
{ id: 2, firstName: 'Jane' },
|
|
47
|
+
];
|
|
48
|
+
const result = sqlFrom(data, {
|
|
49
|
+
columns: { id: 'user_id', firstName: 'name' },
|
|
50
|
+
});
|
|
51
|
+
expect(result).toBe('(SELECT 1 AS "user_id", \'John\' AS "name") ' +
|
|
52
|
+
'UNION ALL ' +
|
|
53
|
+
'(SELECT 2 AS "user_id", \'Jane\' AS "name")');
|
|
54
|
+
});
|
|
55
|
+
it('should handle column subset selection', () => {
|
|
56
|
+
const data = [
|
|
57
|
+
{ id: 1, name: 'John', age: 30 },
|
|
58
|
+
{ id: 2, name: 'Jane', age: 25 },
|
|
59
|
+
];
|
|
60
|
+
const result = sqlFrom(data, { columns: ['id', 'name'] });
|
|
61
|
+
expect(result).toBe('(SELECT 1 AS "id", \'John\' AS "name") ' +
|
|
62
|
+
'UNION ALL ' +
|
|
63
|
+
'(SELECT 2 AS "id", \'Jane\' AS "name")');
|
|
64
|
+
});
|
|
65
|
+
it('should throw error for empty column set', () => {
|
|
66
|
+
const data = [];
|
|
67
|
+
expect(() => sqlFrom(data)).toThrow('Can not create table from empty column set.');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as arrow from 'apache-arrow';
|
|
2
|
+
/**
|
|
3
|
+
* Converts an Arrow table to a JSON-compatible array of objects
|
|
4
|
+
* @see https://duckdb.org/docs/api/wasm/query.html#arrow-table-to-json
|
|
5
|
+
* @see https://github.com/apache/arrow/issues/37856
|
|
6
|
+
*/
|
|
7
|
+
export declare function arrowTableToJson(table: arrow.Table): Record<string, unknown>[];
|
|
8
|
+
//# sourceMappingURL=arrow-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrow-utils.d.ts","sourceRoot":"","sources":["../src/arrow-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,CAAC,KAAK,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAQ3B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an Arrow table to a JSON-compatible array of objects
|
|
3
|
+
* @see https://duckdb.org/docs/api/wasm/query.html#arrow-table-to-json
|
|
4
|
+
* @see https://github.com/apache/arrow/issues/37856
|
|
5
|
+
*/
|
|
6
|
+
export function arrowTableToJson(table) {
|
|
7
|
+
return table.toArray().map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => {
|
|
8
|
+
return [key, convertValue(value)];
|
|
9
|
+
})));
|
|
10
|
+
}
|
|
11
|
+
function convertValue(value) {
|
|
12
|
+
if (typeof value === 'bigint') {
|
|
13
|
+
if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) {
|
|
14
|
+
return Number(value);
|
|
15
|
+
}
|
|
16
|
+
return String(value);
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === 'number') {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
return String(value);
|
|
22
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqlrooms/duckdb",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"private": false,
|
|
9
|
+
"author": "Ilya Boyandin <ilya@boyandin.me>",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/sqlrooms/sqlrooms.git"
|
|
14
|
+
},
|
|
9
15
|
"files": [
|
|
10
16
|
"dist"
|
|
11
17
|
],
|
|
@@ -16,12 +22,20 @@
|
|
|
16
22
|
"@duckdb/duckdb-wasm": "^1.29.0",
|
|
17
23
|
"apache-arrow": "^18.1.0"
|
|
18
24
|
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@sqlrooms/jest-config": "0.1.0",
|
|
27
|
+
"@types/jest": "^29.5.12",
|
|
28
|
+
"jest": "^29.7.0",
|
|
29
|
+
"ts-jest": "^29.1.2"
|
|
30
|
+
},
|
|
19
31
|
"scripts": {
|
|
20
32
|
"dev": "tsc -w",
|
|
21
33
|
"build": "tsc",
|
|
22
34
|
"lint": "eslint .",
|
|
23
35
|
"typecheck": "tsc --noEmit",
|
|
24
|
-
"typedoc": "typedoc"
|
|
36
|
+
"typedoc": "typedoc",
|
|
37
|
+
"test": "jest",
|
|
38
|
+
"test:watch": "jest --watch"
|
|
25
39
|
},
|
|
26
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "2d44f6636dbd53d18c32a422351e93caa182ada6"
|
|
27
41
|
}
|