cl-orm 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/.editorconfig +12 -0
- package/.gitattributes +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +34 -0
- package/.vscode/settings.json +14 -0
- package/README.md +105 -0
- package/mcr.config.ts +12 -0
- package/package.json +30 -0
- package/src/drivers/_utils.ts +31 -0
- package/src/drivers/d1.ts +70 -0
- package/src/drivers/do.ts +81 -0
- package/src/index.ts +13 -0
- package/src/queries/_utils.ts +10 -0
- package/src/queries/delete.ts +26 -0
- package/src/queries/insert.ts +22 -0
- package/src/queries/select.ts +64 -0
- package/src/queries/update.ts +28 -0
- package/src/queries/where/operators.ts +96 -0
- package/src/queries/where/where.ts +50 -0
- package/src/types.ts +76 -0
- package/test/__fixtures__/d1/worker.ts +70 -0
- package/test/__fixtures__/d1/wrangler.jsonc +13 -0
- package/test/__fixtures__/do/worker.ts +106 -0
- package/test/__fixtures__/do/wrangler.jsonc +20 -0
- package/test/e2e/d1.test.ts +113 -0
- package/test/e2e/do.test.ts +116 -0
- package/test/unit/buildDelete.test.ts +36 -0
- package/test/unit/buildInsert.test.ts +36 -0
- package/test/unit/buildSelect.test.ts +137 -0
- package/test/unit/buildUpdate.test.ts +34 -0
- package/test/unit/buildWhere.test.ts +249 -0
- package/test/unit/operators.test.ts +98 -0
- package/test/unit/prepare.test.ts +22 -0
- package/test/unit/quoteIdentifier.test.ts +12 -0
- package/test/unit/returnsRows.test.ts +41 -0
- package/test/unit/setMeta.test.ts +31 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +34 -0
- package/website/.prettierignore +4 -0
- package/website/.prettierrc +34 -0
- package/website/README.md +3 -0
- package/website/docs/documentation/connection.mdx +123 -0
- package/website/docs/documentation/queries/_category_.json +7 -0
- package/website/docs/documentation/queries/delete.mdx +51 -0
- package/website/docs/documentation/queries/insert.mdx +63 -0
- package/website/docs/documentation/queries/operators/_category_.json +7 -0
- package/website/docs/documentation/queries/operators/between.mdx +17 -0
- package/website/docs/documentation/queries/operators/eq.mdx +22 -0
- package/website/docs/documentation/queries/operators/gt.mdx +22 -0
- package/website/docs/documentation/queries/operators/gte.mdx +22 -0
- package/website/docs/documentation/queries/operators/in.mdx +28 -0
- package/website/docs/documentation/queries/operators/is-not-null.mdx +22 -0
- package/website/docs/documentation/queries/operators/is-null.mdx +22 -0
- package/website/docs/documentation/queries/operators/like.mdx +27 -0
- package/website/docs/documentation/queries/operators/lt.mdx +22 -0
- package/website/docs/documentation/queries/operators/lte.mdx +22 -0
- package/website/docs/documentation/queries/operators/ne.mdx +22 -0
- package/website/docs/documentation/queries/operators/not-between.mdx +17 -0
- package/website/docs/documentation/queries/operators/not-like.mdx +27 -0
- package/website/docs/documentation/queries/operators/notIn.mdx +28 -0
- package/website/docs/documentation/queries/select.mdx +294 -0
- package/website/docs/documentation/queries/update.mdx +61 -0
- package/website/docs/documentation/queries/where.mdx +176 -0
- package/website/docs/index.mdx +228 -0
- package/website/docusaurus.config.ts +82 -0
- package/website/package-lock.json +21348 -0
- package/website/package.json +59 -0
- package/website/plugins/locale.ts +16 -0
- package/website/sidebars.ts +18 -0
- package/website/src/components/FAQ.tsx +35 -0
- package/website/src/components/Loading.tsx +4 -0
- package/website/src/components/PageTitle.tsx +29 -0
- package/website/src/css/_faq.scss +39 -0
- package/website/src/css/_loading.scss +43 -0
- package/website/src/css/_mixins.scss +19 -0
- package/website/src/css/custom.scss +149 -0
- package/website/src/pages/index.tsx +17 -0
- package/website/static/.nojekyll +0 -0
- package/website/static/img/favicon.svg +1 -0
- package/website/test/unit/check-extensions.test.ts +48 -0
- package/website/tsconfig.json +14 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { buildSelect } from '../../src/queries/select.js';
|
|
3
|
+
import { OP } from '../../src/queries/where/operators.js';
|
|
4
|
+
|
|
5
|
+
describe('buildSelect', () => {
|
|
6
|
+
it('should build a basic select', () => {
|
|
7
|
+
const result = buildSelect({ table: 'users' });
|
|
8
|
+
assert.strictEqual(result.sql, 'SELECT * FROM `users`');
|
|
9
|
+
assert.deepStrictEqual(result.params, []);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should build select with specific columns', () => {
|
|
13
|
+
const result = buildSelect({ table: 'users', columns: ['name', 'email'] });
|
|
14
|
+
assert.strictEqual(result.sql, 'SELECT `name`, `email` FROM `users`');
|
|
15
|
+
assert.deepStrictEqual(result.params, []);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should build select with string column', () => {
|
|
19
|
+
const result = buildSelect({ table: 'users', columns: 'COUNT(*)' });
|
|
20
|
+
assert.strictEqual(result.sql, 'SELECT COUNT(*) FROM `users`');
|
|
21
|
+
assert.deepStrictEqual(result.params, []);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should build select with distinct', () => {
|
|
25
|
+
const result = buildSelect({ table: 'users', distinct: true });
|
|
26
|
+
assert.strictEqual(result.sql, 'SELECT DISTINCT * FROM `users`');
|
|
27
|
+
assert.deepStrictEqual(result.params, []);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should build select with where clause', () => {
|
|
31
|
+
const result = buildSelect({
|
|
32
|
+
table: 'users',
|
|
33
|
+
where: OP.eq('name', 'Alice'),
|
|
34
|
+
});
|
|
35
|
+
assert.strictEqual(result.sql, 'SELECT * FROM `users` WHERE `name` = ?');
|
|
36
|
+
assert.deepStrictEqual(result.params, ['Alice']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should build select with limit', () => {
|
|
40
|
+
const result = buildSelect({ table: 'users', limit: 10 });
|
|
41
|
+
assert.strictEqual(result.sql, 'SELECT * FROM `users` LIMIT ?');
|
|
42
|
+
assert.deepStrictEqual(result.params, [10]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should build select with offset', () => {
|
|
46
|
+
const result = buildSelect({ table: 'users', limit: 10, offset: 5 });
|
|
47
|
+
assert.strictEqual(result.sql, 'SELECT * FROM `users` LIMIT ? OFFSET ?');
|
|
48
|
+
assert.deepStrictEqual(result.params, [10, 5]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should build select with orderBy', () => {
|
|
52
|
+
const result = buildSelect({ table: 'users', orderBy: ['name', 'DESC'] });
|
|
53
|
+
assert.strictEqual(
|
|
54
|
+
result.sql,
|
|
55
|
+
'SELECT * FROM `users` ORDER BY `name` DESC'
|
|
56
|
+
);
|
|
57
|
+
assert.deepStrictEqual(result.params, []);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should build select with orderBy default ASC', () => {
|
|
61
|
+
const result = buildSelect({ table: 'users', orderBy: ['name'] });
|
|
62
|
+
assert.strictEqual(result.sql, 'SELECT * FROM `users` ORDER BY `name` ASC');
|
|
63
|
+
assert.deepStrictEqual(result.params, []);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should build select with groupBy', () => {
|
|
67
|
+
const result = buildSelect({ table: 'users', groupBy: 'name' });
|
|
68
|
+
assert.strictEqual(result.sql, 'SELECT * FROM `users` GROUP BY `name`');
|
|
69
|
+
assert.deepStrictEqual(result.params, []);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should build select with join', () => {
|
|
73
|
+
const result = buildSelect({
|
|
74
|
+
table: 'users',
|
|
75
|
+
join: {
|
|
76
|
+
type: 'inner',
|
|
77
|
+
table: 'orders',
|
|
78
|
+
on: { a: 'users.id', b: 'orders.user_id' },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
assert.strictEqual(
|
|
82
|
+
result.sql,
|
|
83
|
+
'SELECT * FROM `users` INNER JOIN `orders` ON `users`.`id` = `orders`.`user_id`'
|
|
84
|
+
);
|
|
85
|
+
assert.deepStrictEqual(result.params, []);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should build select with outer join', () => {
|
|
89
|
+
const result = buildSelect({
|
|
90
|
+
table: 'users',
|
|
91
|
+
join: {
|
|
92
|
+
type: 'left',
|
|
93
|
+
table: 'orders',
|
|
94
|
+
on: { a: 'users.id', b: 'orders.user_id' },
|
|
95
|
+
outer: true,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
assert.strictEqual(
|
|
99
|
+
result.sql,
|
|
100
|
+
'SELECT * FROM `users` LEFT OUTER JOIN `orders` ON `users`.`id` = `orders`.`user_id`'
|
|
101
|
+
);
|
|
102
|
+
assert.deepStrictEqual(result.params, []);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should build select with multiple joins', () => {
|
|
106
|
+
const result = buildSelect({
|
|
107
|
+
table: 'users',
|
|
108
|
+
join: [
|
|
109
|
+
{
|
|
110
|
+
type: 'inner',
|
|
111
|
+
table: 'orders',
|
|
112
|
+
on: { a: 'users.id', b: 'orders.user_id' },
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'left',
|
|
116
|
+
table: 'items',
|
|
117
|
+
on: { a: 'orders.id', b: 'items.order_id' },
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
assert.strictEqual(
|
|
122
|
+
result.sql,
|
|
123
|
+
'SELECT * FROM `users` INNER JOIN `orders` ON `users`.`id` = `orders`.`user_id` LEFT JOIN `items` ON `orders`.`id` = `items`.`order_id`'
|
|
124
|
+
);
|
|
125
|
+
assert.deepStrictEqual(result.params, []);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should build select with params', () => {
|
|
129
|
+
const result = buildSelect({
|
|
130
|
+
table: 'users',
|
|
131
|
+
where: 'name = ?',
|
|
132
|
+
params: ['Alice'],
|
|
133
|
+
});
|
|
134
|
+
assert.strictEqual(result.sql, 'SELECT * FROM `users` WHERE name = ?');
|
|
135
|
+
assert.deepStrictEqual(result.params, ['Alice']);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { buildUpdate } from '../../src/queries/update.js';
|
|
3
|
+
import { OP } from '../../src/queries/where/operators.js';
|
|
4
|
+
|
|
5
|
+
describe('buildUpdate', () => {
|
|
6
|
+
it('should build a basic update', () => {
|
|
7
|
+
const result = buildUpdate({ table: 'users', set: { name: 'Bob' } });
|
|
8
|
+
assert.strictEqual(result.sql, 'UPDATE `users` SET `name` = ?');
|
|
9
|
+
assert.deepStrictEqual(result.params, ['Bob']);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should build update with where clause', () => {
|
|
13
|
+
const result = buildUpdate({
|
|
14
|
+
table: 'users',
|
|
15
|
+
set: { name: 'Bob' },
|
|
16
|
+
where: OP.eq('name', 'Alice'),
|
|
17
|
+
});
|
|
18
|
+
assert.strictEqual(
|
|
19
|
+
result.sql,
|
|
20
|
+
'UPDATE `users` SET `name` = ? WHERE `name` = ?'
|
|
21
|
+
);
|
|
22
|
+
assert.deepStrictEqual(result.params, ['Bob', 'Alice']);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should build update with extra params', () => {
|
|
26
|
+
const result = buildUpdate({
|
|
27
|
+
table: 'users',
|
|
28
|
+
set: { name: 'Bob' },
|
|
29
|
+
params: ['extra'],
|
|
30
|
+
});
|
|
31
|
+
assert.strictEqual(result.sql, 'UPDATE `users` SET `name` = ?');
|
|
32
|
+
assert.deepStrictEqual(result.params, ['Bob', 'extra']);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { OP } from '../../src/queries/where/operators.js';
|
|
3
|
+
import { buildWhere } from '../../src/queries/where/where.js';
|
|
4
|
+
|
|
5
|
+
describe('buildWhere', () => {
|
|
6
|
+
it('should handle raw string', () => {
|
|
7
|
+
const result = buildWhere('id = 1');
|
|
8
|
+
assert.strictEqual(result.sql, 'id = 1');
|
|
9
|
+
assert.deepStrictEqual(result.params, []);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should handle a single eq condition', () => {
|
|
13
|
+
const result = buildWhere(OP.eq('name', 'Alice'));
|
|
14
|
+
assert.strictEqual(result.sql, '`name` = ?');
|
|
15
|
+
assert.deepStrictEqual(result.params, ['Alice']);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should handle a single ne condition', () => {
|
|
19
|
+
const result = buildWhere(OP.ne('status', 'inactive'));
|
|
20
|
+
assert.strictEqual(result.sql, '`status` != ?');
|
|
21
|
+
assert.deepStrictEqual(result.params, ['inactive']);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should handle a single gt condition', () => {
|
|
25
|
+
const result = buildWhere(OP.gt('age', 18));
|
|
26
|
+
assert.strictEqual(result.sql, '`age` > ?');
|
|
27
|
+
assert.deepStrictEqual(result.params, [18]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle a single lt condition', () => {
|
|
31
|
+
const result = buildWhere(OP.lt('price', 100));
|
|
32
|
+
assert.strictEqual(result.sql, '`price` < ?');
|
|
33
|
+
assert.deepStrictEqual(result.params, [100]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should handle a single gte condition', () => {
|
|
37
|
+
const result = buildWhere(OP.gte('score', 90));
|
|
38
|
+
assert.strictEqual(result.sql, '`score` >= ?');
|
|
39
|
+
assert.deepStrictEqual(result.params, [90]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle a single lte condition', () => {
|
|
43
|
+
const result = buildWhere(OP.lte('quantity', 0));
|
|
44
|
+
assert.strictEqual(result.sql, '`quantity` <= ?');
|
|
45
|
+
assert.deepStrictEqual(result.params, [0]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle a single like condition', () => {
|
|
49
|
+
const result = buildWhere(OP.like('name', '%Ali%'));
|
|
50
|
+
assert.strictEqual(result.sql, '`name` LIKE ?');
|
|
51
|
+
assert.deepStrictEqual(result.params, ['%Ali%']);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle a single notLike condition', () => {
|
|
55
|
+
const result = buildWhere(OP.notLike('name', '%Bot%'));
|
|
56
|
+
assert.strictEqual(result.sql, '`name` NOT LIKE ?');
|
|
57
|
+
assert.deepStrictEqual(result.params, ['%Bot%']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle a single isNull condition', () => {
|
|
61
|
+
const result = buildWhere(OP.isNull('email'));
|
|
62
|
+
assert.strictEqual(result.sql, '`email` IS NULL');
|
|
63
|
+
assert.deepStrictEqual(result.params, []);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle a single isNotNull condition', () => {
|
|
67
|
+
const result = buildWhere(OP.isNotNull('email'));
|
|
68
|
+
assert.strictEqual(result.sql, '`email` IS NOT NULL');
|
|
69
|
+
assert.deepStrictEqual(result.params, []);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle a single in condition with values', () => {
|
|
73
|
+
const result = buildWhere(OP.in('id', [1, 2, 3]));
|
|
74
|
+
assert.strictEqual(result.sql, '`id` IN (?, ?, ?)');
|
|
75
|
+
assert.deepStrictEqual(result.params, [1, 2, 3]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle a single in condition with subquery', () => {
|
|
79
|
+
const result = buildWhere(
|
|
80
|
+
OP.in('id', 'SELECT id FROM other WHERE x = ?', [42])
|
|
81
|
+
);
|
|
82
|
+
assert.strictEqual(
|
|
83
|
+
result.sql,
|
|
84
|
+
'`id` IN (SELECT id FROM other WHERE x = ?)'
|
|
85
|
+
);
|
|
86
|
+
assert.deepStrictEqual(result.params, [42]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle a single notIn condition with values', () => {
|
|
90
|
+
const result = buildWhere(OP.notIn('id', [4, 5]));
|
|
91
|
+
assert.strictEqual(result.sql, '`id` NOT IN (?, ?)');
|
|
92
|
+
assert.deepStrictEqual(result.params, [4, 5]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle a single notIn condition with subquery', () => {
|
|
96
|
+
const result = buildWhere(OP.notIn('id', 'SELECT id FROM blocked', []));
|
|
97
|
+
assert.strictEqual(result.sql, '`id` NOT IN (SELECT id FROM blocked)');
|
|
98
|
+
assert.deepStrictEqual(result.params, []);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle a single between condition', () => {
|
|
102
|
+
const result = buildWhere(OP.between('age', [18, 65]));
|
|
103
|
+
assert.strictEqual(result.sql, '`age` BETWEEN ? AND ?');
|
|
104
|
+
assert.deepStrictEqual(result.params, [18, 65]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should handle a single notBetween condition', () => {
|
|
108
|
+
const result = buildWhere(OP.notBetween('price', [0, 10]));
|
|
109
|
+
assert.strictEqual(result.sql, '`price` NOT BETWEEN ? AND ?');
|
|
110
|
+
assert.deepStrictEqual(result.params, [0, 10]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should handle array with AND connector', () => {
|
|
114
|
+
const result = buildWhere([
|
|
115
|
+
OP.eq('name', 'Alice'),
|
|
116
|
+
'AND',
|
|
117
|
+
OP.gt('age', 18),
|
|
118
|
+
]);
|
|
119
|
+
assert.strictEqual(result.sql, '`name` = ? AND `age` > ?');
|
|
120
|
+
assert.deepStrictEqual(result.params, ['Alice', 18]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should handle array with OR connector', () => {
|
|
124
|
+
const result = buildWhere([
|
|
125
|
+
OP.eq('role', 'admin'),
|
|
126
|
+
'OR',
|
|
127
|
+
OP.eq('role', 'superadmin'),
|
|
128
|
+
]);
|
|
129
|
+
assert.strictEqual(result.sql, '`role` = ? OR `role` = ?');
|
|
130
|
+
assert.deepStrictEqual(result.params, ['admin', 'superadmin']);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle array with XOR connector', () => {
|
|
134
|
+
const result = buildWhere([OP.eq('a', 1), 'XOR', OP.eq('b', 2)]);
|
|
135
|
+
assert.strictEqual(result.sql, '`a` = ? XOR `b` = ?');
|
|
136
|
+
assert.deepStrictEqual(result.params, [1, 2]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle array with NOT connector', () => {
|
|
140
|
+
const result = buildWhere(['NOT', OP.eq('active', 0)]);
|
|
141
|
+
assert.strictEqual(result.sql, 'NOT `active` = ?');
|
|
142
|
+
assert.deepStrictEqual(result.params, [0]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should handle multiple AND conditions', () => {
|
|
146
|
+
const result = buildWhere([
|
|
147
|
+
OP.eq('name', 'Alice'),
|
|
148
|
+
'AND',
|
|
149
|
+
OP.gt('age', 18),
|
|
150
|
+
'AND',
|
|
151
|
+
OP.isNotNull('email'),
|
|
152
|
+
]);
|
|
153
|
+
assert.strictEqual(
|
|
154
|
+
result.sql,
|
|
155
|
+
'`name` = ? AND `age` > ? AND `email` IS NOT NULL'
|
|
156
|
+
);
|
|
157
|
+
assert.deepStrictEqual(result.params, ['Alice', 18]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should handle mixed AND and OR connectors', () => {
|
|
161
|
+
const result = buildWhere([
|
|
162
|
+
OP.eq('status', 'active'),
|
|
163
|
+
'AND',
|
|
164
|
+
OP.gt('age', 18),
|
|
165
|
+
'OR',
|
|
166
|
+
OP.eq('role', 'admin'),
|
|
167
|
+
]);
|
|
168
|
+
assert.strictEqual(result.sql, '`status` = ? AND `age` > ? OR `role` = ?');
|
|
169
|
+
assert.deepStrictEqual(result.params, ['active', 18, 'admin']);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should handle nested groups', () => {
|
|
173
|
+
const result = buildWhere([
|
|
174
|
+
OP.eq('a', 1),
|
|
175
|
+
'OR',
|
|
176
|
+
[OP.eq('b', 2), 'AND', OP.eq('c', 3)],
|
|
177
|
+
]);
|
|
178
|
+
assert.strictEqual(result.sql, '`a` = ? OR (`b` = ? AND `c` = ?)');
|
|
179
|
+
assert.deepStrictEqual(result.params, [1, 2, 3]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should handle deeply nested groups', () => {
|
|
183
|
+
const result = buildWhere([
|
|
184
|
+
OP.eq('a', 1),
|
|
185
|
+
'AND',
|
|
186
|
+
[OP.eq('b', 2), 'OR', [OP.eq('c', 3), 'AND', OP.eq('d', 4)]],
|
|
187
|
+
]);
|
|
188
|
+
assert.strictEqual(
|
|
189
|
+
result.sql,
|
|
190
|
+
'`a` = ? AND (`b` = ? OR (`c` = ? AND `d` = ?))'
|
|
191
|
+
);
|
|
192
|
+
assert.deepStrictEqual(result.params, [1, 2, 3, 4]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should handle multiple nested groups', () => {
|
|
196
|
+
const result = buildWhere([
|
|
197
|
+
[OP.eq('a', 1), 'AND', OP.eq('b', 2)],
|
|
198
|
+
'OR',
|
|
199
|
+
[OP.eq('c', 3), 'AND', OP.eq('d', 4)],
|
|
200
|
+
]);
|
|
201
|
+
assert.strictEqual(
|
|
202
|
+
result.sql,
|
|
203
|
+
'(`a` = ? AND `b` = ?) OR (`c` = ? AND `d` = ?)'
|
|
204
|
+
);
|
|
205
|
+
assert.deepStrictEqual(result.params, [1, 2, 3, 4]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should handle nested group with mixed operators', () => {
|
|
209
|
+
const result = buildWhere([
|
|
210
|
+
OP.isNotNull('email'),
|
|
211
|
+
'AND',
|
|
212
|
+
[OP.like('name', '%Alice%'), 'OR', OP.in('role', ['admin', 'editor'])],
|
|
213
|
+
]);
|
|
214
|
+
assert.strictEqual(
|
|
215
|
+
result.sql,
|
|
216
|
+
'`email` IS NOT NULL AND (`name` LIKE ? OR `role` IN (?, ?))'
|
|
217
|
+
);
|
|
218
|
+
assert.deepStrictEqual(result.params, ['%Alice%', 'admin', 'editor']);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should handle between inside a group with AND', () => {
|
|
222
|
+
const result = buildWhere([
|
|
223
|
+
OP.between('age', [18, 65]),
|
|
224
|
+
'AND',
|
|
225
|
+
OP.eq('active', 1),
|
|
226
|
+
]);
|
|
227
|
+
assert.strictEqual(result.sql, '`age` BETWEEN ? AND ? AND `active` = ?');
|
|
228
|
+
assert.deepStrictEqual(result.params, [18, 65, 1]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should handle notIn with nested group', () => {
|
|
232
|
+
const result = buildWhere([
|
|
233
|
+
OP.notIn('id', [10, 20, 30]),
|
|
234
|
+
'AND',
|
|
235
|
+
[OP.gte('score', 50), 'OR', OP.isNull('score')],
|
|
236
|
+
]);
|
|
237
|
+
assert.strictEqual(
|
|
238
|
+
result.sql,
|
|
239
|
+
'`id` NOT IN (?, ?, ?) AND (`score` >= ? OR `score` IS NULL)'
|
|
240
|
+
);
|
|
241
|
+
assert.deepStrictEqual(result.params, [10, 20, 30, 50]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should handle single condition inside an array', () => {
|
|
245
|
+
const result = buildWhere([OP.eq('x', 1)]);
|
|
246
|
+
assert.strictEqual(result.sql, '`x` = ?');
|
|
247
|
+
assert.deepStrictEqual(result.params, [1]);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { OP } from '../../src/queries/where/operators.js';
|
|
3
|
+
|
|
4
|
+
describe('OP (operators)', () => {
|
|
5
|
+
it('should build eq condition', () => {
|
|
6
|
+
const cond = OP.eq('name', 'Alice');
|
|
7
|
+
assert.strictEqual(cond.condition, '`name` = ?');
|
|
8
|
+
assert.deepStrictEqual(cond.params, ['Alice']);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should build ne condition', () => {
|
|
12
|
+
const cond = OP.ne('name', 'Alice');
|
|
13
|
+
assert.strictEqual(cond.condition, '`name` != ?');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should build gt condition', () => {
|
|
17
|
+
const cond = OP.gt('age', 18);
|
|
18
|
+
assert.strictEqual(cond.condition, '`age` > ?');
|
|
19
|
+
assert.deepStrictEqual(cond.params, [18]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should build lt condition', () => {
|
|
23
|
+
const cond = OP.lt('age', 18);
|
|
24
|
+
assert.strictEqual(cond.condition, '`age` < ?');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should build gte condition', () => {
|
|
28
|
+
const cond = OP.gte('age', 18);
|
|
29
|
+
assert.strictEqual(cond.condition, '`age` >= ?');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should build lte condition', () => {
|
|
33
|
+
const cond = OP.lte('age', 18);
|
|
34
|
+
assert.strictEqual(cond.condition, '`age` <= ?');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should build like condition', () => {
|
|
38
|
+
const cond = OP.like('name', '%Ali%');
|
|
39
|
+
assert.strictEqual(cond.condition, '`name` LIKE ?');
|
|
40
|
+
assert.deepStrictEqual(cond.params, ['%Ali%']);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should build notLike condition', () => {
|
|
44
|
+
const cond = OP.notLike('name', '%Ali%');
|
|
45
|
+
assert.strictEqual(cond.condition, '`name` NOT LIKE ?');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should build isNull condition', () => {
|
|
49
|
+
const cond = OP.isNull('email');
|
|
50
|
+
assert.strictEqual(cond.condition, '`email` IS NULL');
|
|
51
|
+
assert.deepStrictEqual(cond.params, []);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should build isNotNull condition', () => {
|
|
55
|
+
const cond = OP.isNotNull('email');
|
|
56
|
+
assert.strictEqual(cond.condition, '`email` IS NOT NULL');
|
|
57
|
+
assert.deepStrictEqual(cond.params, []);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should build in condition with values', () => {
|
|
61
|
+
const cond = OP.in('id', [1, 2, 3]);
|
|
62
|
+
assert.strictEqual(cond.condition, '`id` IN (?, ?, ?)');
|
|
63
|
+
assert.deepStrictEqual(cond.params, [1, 2, 3]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should build in condition with subquery', () => {
|
|
67
|
+
const cond = OP.in('id', 'SELECT id FROM other WHERE x = ?', [42]);
|
|
68
|
+
assert.strictEqual(
|
|
69
|
+
cond.condition,
|
|
70
|
+
'`id` IN (SELECT id FROM other WHERE x = ?)'
|
|
71
|
+
);
|
|
72
|
+
assert.deepStrictEqual(cond.params, [42]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should build notIn condition with values', () => {
|
|
76
|
+
const cond = OP.notIn('id', [1, 2]);
|
|
77
|
+
assert.strictEqual(cond.condition, '`id` NOT IN (?, ?)');
|
|
78
|
+
assert.deepStrictEqual(cond.params, [1, 2]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should build notIn condition with subquery', () => {
|
|
82
|
+
const cond = OP.notIn('id', 'SELECT id FROM other', []);
|
|
83
|
+
assert.strictEqual(cond.condition, '`id` NOT IN (SELECT id FROM other)');
|
|
84
|
+
assert.deepStrictEqual(cond.params, []);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should build between condition', () => {
|
|
88
|
+
const cond = OP.between('age', [18, 65]);
|
|
89
|
+
assert.strictEqual(cond.condition, '`age` BETWEEN ? AND ?');
|
|
90
|
+
assert.deepStrictEqual(cond.params, [18, 65]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should build notBetween condition', () => {
|
|
94
|
+
const cond = OP.notBetween('age', [18, 65]);
|
|
95
|
+
assert.strictEqual(cond.condition, '`age` NOT BETWEEN ? AND ?');
|
|
96
|
+
assert.deepStrictEqual(cond.params, [18, 65]);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { prepare } from '../../src/drivers/_utils.js';
|
|
3
|
+
|
|
4
|
+
describe('prepare', () => {
|
|
5
|
+
const mockBound = {} as D1PreparedStatement;
|
|
6
|
+
const mockStatement = {
|
|
7
|
+
bind: (..._params: unknown[]) => mockBound,
|
|
8
|
+
} as D1PreparedStatement;
|
|
9
|
+
const mockDb = {
|
|
10
|
+
prepare: (_sql: string) => mockStatement,
|
|
11
|
+
} as unknown as D1Database;
|
|
12
|
+
|
|
13
|
+
it('should bind params when params are provided', () => {
|
|
14
|
+
const result = prepare(mockDb, 'SELECT * FROM users WHERE id = ?', [1]);
|
|
15
|
+
assert.strictEqual(result, mockBound);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return statement without bind when no params', () => {
|
|
19
|
+
const result = prepare(mockDb, 'SELECT * FROM users', []);
|
|
20
|
+
assert.strictEqual(result, mockStatement);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { quoteIdentifier } from '../../src/queries/_utils.js';
|
|
3
|
+
|
|
4
|
+
describe('quoteIdentifier', () => {
|
|
5
|
+
it('should quote a simple name', () => {
|
|
6
|
+
assert.strictEqual(quoteIdentifier('users'), '`users`');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should quote a dotted name', () => {
|
|
10
|
+
assert.strictEqual(quoteIdentifier('users.id'), '`users`.`id`');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { returnsRows } from '../../src/drivers/_utils.js';
|
|
3
|
+
|
|
4
|
+
describe('returnsRows', () => {
|
|
5
|
+
it('should return true for SELECT', () => {
|
|
6
|
+
assert.strictEqual(returnsRows('SELECT * FROM users'), true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return true for WITH', () => {
|
|
10
|
+
assert.strictEqual(
|
|
11
|
+
returnsRows('WITH cte AS (SELECT 1) SELECT * FROM cte'),
|
|
12
|
+
true
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return true for PRAGMA', () => {
|
|
17
|
+
assert.strictEqual(returnsRows('PRAGMA table_info(users)'), true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should return true for RETURNING', () => {
|
|
21
|
+
assert.strictEqual(
|
|
22
|
+
returnsRows('INSERT INTO users (name) VALUES (?) RETURNING *'),
|
|
23
|
+
true
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return false for INSERT', () => {
|
|
28
|
+
assert.strictEqual(
|
|
29
|
+
returnsRows('INSERT INTO users (name) VALUES (?)'),
|
|
30
|
+
false
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return false for UPDATE', () => {
|
|
35
|
+
assert.strictEqual(returnsRows('UPDATE users SET name = ?'), false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return false for DELETE', () => {
|
|
39
|
+
assert.strictEqual(returnsRows('DELETE FROM users'), false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { assert, describe, it } from 'poku';
|
|
2
|
+
import { setMeta } from '../../src/drivers/_utils.js';
|
|
3
|
+
|
|
4
|
+
describe('setMeta', () => {
|
|
5
|
+
it('should map cursor fields to Meta object', () => {
|
|
6
|
+
const mockCursor = {
|
|
7
|
+
rowsRead: 5,
|
|
8
|
+
rowsWritten: 2,
|
|
9
|
+
} as SqlStorageCursor<Record<string, SqlStorageValue>>;
|
|
10
|
+
|
|
11
|
+
const meta = setMeta(mockCursor);
|
|
12
|
+
assert.strictEqual(meta.duration, 0);
|
|
13
|
+
assert.strictEqual(meta.size_after, 0);
|
|
14
|
+
assert.strictEqual(meta.rows_read, 5);
|
|
15
|
+
assert.strictEqual(meta.rows_written, 2);
|
|
16
|
+
assert.strictEqual(meta.last_row_id, 0);
|
|
17
|
+
assert.strictEqual(meta.changed_db, true);
|
|
18
|
+
assert.strictEqual(meta.changes, 2);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should set changed_db to false when no rows written', () => {
|
|
22
|
+
const mockCursor = {
|
|
23
|
+
rowsRead: 3,
|
|
24
|
+
rowsWritten: 0,
|
|
25
|
+
} as SqlStorageCursor<Record<string, SqlStorageValue>>;
|
|
26
|
+
|
|
27
|
+
const meta = setMeta(mockCursor);
|
|
28
|
+
assert.strictEqual(meta.changed_db, false);
|
|
29
|
+
assert.strictEqual(meta.changes, 0);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"lib": ["es2022", "DOM"],
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"forceConsistentCasingInFileNames": true,
|
|
8
|
+
"isolatedModules": true,
|
|
9
|
+
"allowJs": false,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"alwaysStrict": true,
|
|
12
|
+
"strictFunctionTypes": true,
|
|
13
|
+
"strictNullChecks": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": false,
|
|
16
|
+
"allowUnreachableCode": false,
|
|
17
|
+
"allowUnusedLabels": false,
|
|
18
|
+
"noImplicitAny": true,
|
|
19
|
+
"removeComments": false,
|
|
20
|
+
"sourceMap": false,
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"noEmitOnError": true,
|
|
23
|
+
"declaration": true,
|
|
24
|
+
"allowSyntheticDefaultImports": true,
|
|
25
|
+
"skipLibCheck": true,
|
|
26
|
+
"outDir": "lib",
|
|
27
|
+
"declarationDir": "lib",
|
|
28
|
+
"typeRoots": [
|
|
29
|
+
"node_modules/@types",
|
|
30
|
+
"node_modules/@cloudflare/workers-types"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"include": ["src", "test"]
|
|
34
|
+
}
|