easy-postgresql 0.0.1
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/LICENSE +21 -0
- package/README.md +45 -0
- package/SECURITY.md +15 -0
- package/database/procedure.ts +129 -0
- package/database/query.ts +78 -0
- package/database/sql_types.d.ts +52 -0
- package/database/sql_types.ts +90 -0
- package/database/table.ts +378 -0
- package/database.ts +26 -0
- package/dist/database/procedure.d.ts +26 -0
- package/dist/database/procedure.js +107 -0
- package/dist/database/procedure.js.map +1 -0
- package/dist/database/query.d.ts +13 -0
- package/dist/database/query.js +79 -0
- package/dist/database/query.js.map +1 -0
- package/dist/database/sql_types.d.ts +48 -0
- package/dist/database/sql_types.js +50 -0
- package/dist/database/sql_types.js.map +1 -0
- package/dist/database/table.d.ts +54 -0
- package/dist/database/table.js +308 -0
- package/dist/database/table.js.map +1 -0
- package/dist/database.d.ts +24 -0
- package/dist/database.js +28 -0
- package/dist/database.js.map +1 -0
- package/dist/operations/config.d.ts +22 -0
- package/dist/operations/config.js +30 -0
- package/dist/operations/config.js.map +1 -0
- package/dist/operations/connect_to_server.d.ts +28 -0
- package/dist/operations/connect_to_server.js +108 -0
- package/dist/operations/connect_to_server.js.map +1 -0
- package/dist/operations/data_types.d.ts +6 -0
- package/dist/operations/data_types.js +120 -0
- package/dist/operations/data_types.js.map +1 -0
- package/dist/operations/get_pieces.d.ts +29 -0
- package/dist/operations/get_pieces.js +56 -0
- package/dist/operations/get_pieces.js.map +1 -0
- package/dist/operations/log.d.ts +12 -0
- package/dist/operations/log.js +27 -0
- package/dist/operations/log.js.map +1 -0
- package/dist/operations/procedure_execute.d.ts +18 -0
- package/dist/operations/procedure_execute.js +53 -0
- package/dist/operations/procedure_execute.js.map +1 -0
- package/operations/config.ts +28 -0
- package/operations/connect_to_server.ts +111 -0
- package/operations/data_types.ts +129 -0
- package/operations/get_pieces.ts +63 -0
- package/operations/log.ts +27 -0
- package/operations/procedure_execute.ts +58 -0
- package/package.json +55 -0
- package/simple/advanced-test.js +631 -0
- package/simple/config.simple.js +9 -0
- package/simple/config.simple.ts +9 -0
- package/simple/test.js +398 -0
- package/simple/test.ts +392 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
const EasyPostgresql = require('../dist/database.js');
|
|
2
|
+
require('colors');
|
|
3
|
+
|
|
4
|
+
const sqlConfig = {
|
|
5
|
+
user: 'postgres',
|
|
6
|
+
password: 'DbmjAzRbdvYQPjwahRudWftWfhWSjJKG',
|
|
7
|
+
host: 'yamanote.proxy.rlwy.net',
|
|
8
|
+
port: 11348,
|
|
9
|
+
database: 'railway'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
console.log('\neasy-postgresql Advanced Test Suite');
|
|
13
|
+
console.log('=====================================');
|
|
14
|
+
console.log('Starting advanced tests...\n');
|
|
15
|
+
|
|
16
|
+
let passed = 0;
|
|
17
|
+
let failed = 0;
|
|
18
|
+
|
|
19
|
+
function assert(condition, name) {
|
|
20
|
+
if (condition) {
|
|
21
|
+
console.log(` PASS: ${name}`.green);
|
|
22
|
+
passed++;
|
|
23
|
+
} else {
|
|
24
|
+
console.log(` FAIL: ${name}`.red);
|
|
25
|
+
failed++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function run() {
|
|
30
|
+
EasyPostgresql.Config.logingMode(true);
|
|
31
|
+
await EasyPostgresql.Connect(sqlConfig);
|
|
32
|
+
|
|
33
|
+
// ============================================================
|
|
34
|
+
// SECTION 1: Multiple Table Relationships (JOIN)
|
|
35
|
+
// ============================================================
|
|
36
|
+
console.log('\n=== Section 1: Multi-table JOIN operations ==='.cyan);
|
|
37
|
+
|
|
38
|
+
const users = EasyPostgresql.Table('adv_users');
|
|
39
|
+
const orders = EasyPostgresql.Table('adv_orders');
|
|
40
|
+
const products = EasyPostgresql.Table('adv_products');
|
|
41
|
+
const orderItems = EasyPostgresql.Table('adv_order_items');
|
|
42
|
+
|
|
43
|
+
await users.functions.remove();
|
|
44
|
+
await orders.functions.remove();
|
|
45
|
+
await products.functions.remove();
|
|
46
|
+
await orderItems.functions.remove();
|
|
47
|
+
|
|
48
|
+
await users.functions.create({
|
|
49
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey() + EasyPostgresql.Types.options.autoIncrement(),
|
|
50
|
+
name: EasyPostgresql.Types.varchar(100) + EasyPostgresql.Types.options.notNull(),
|
|
51
|
+
email: EasyPostgresql.Types.varchar(100) + EasyPostgresql.Types.options.unique(),
|
|
52
|
+
created_at: EasyPostgresql.Types.datetime() + ' ' + 'DEFAULT NOW()'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await products.functions.create({
|
|
56
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey() + EasyPostgresql.Types.options.autoIncrement(),
|
|
57
|
+
name: EasyPostgresql.Types.varchar(100) + EasyPostgresql.Types.options.notNull(),
|
|
58
|
+
price: EasyPostgresql.Types.decimal(10, 2) + EasyPostgresql.Types.options.notNull(),
|
|
59
|
+
stock: EasyPostgresql.Types.int() + ' ' + 'DEFAULT 0'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await orders.functions.create({
|
|
63
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey() + EasyPostgresql.Types.options.autoIncrement(),
|
|
64
|
+
user_id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.notNull() + EasyPostgresql.Types.options.foreignKey('adv_users', 'id'),
|
|
65
|
+
total_amount: EasyPostgresql.Types.decimal(10, 2) + EasyPostgresql.Types.options.notNull(),
|
|
66
|
+
status: EasyPostgresql.Types.varchar(20) + ' ' + "DEFAULT 'pending'",
|
|
67
|
+
created_at: EasyPostgresql.Types.datetime() + ' ' + 'DEFAULT NOW()'
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await orderItems.functions.create({
|
|
71
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey() + EasyPostgresql.Types.options.autoIncrement(),
|
|
72
|
+
order_id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.notNull() + EasyPostgresql.Types.options.foreignKey('adv_orders', 'id'),
|
|
73
|
+
product_id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.notNull() + EasyPostgresql.Types.options.foreignKey('adv_products', 'id'),
|
|
74
|
+
quantity: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.notNull(),
|
|
75
|
+
unit_price: EasyPostgresql.Types.decimal(10, 2) + EasyPostgresql.Types.options.notNull()
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
assert(true, 'All 4 tables created with foreign keys');
|
|
79
|
+
|
|
80
|
+
// Insert data
|
|
81
|
+
const u1 = await users.createOne({ name: 'Alice Johnson', email: 'alice@example.com' });
|
|
82
|
+
const u2 = await users.createOne({ name: 'Bob Smith', email: 'bob@example.com' });
|
|
83
|
+
const u3 = await users.createOne({ name: 'Charlie Brown', email: 'charlie@example.com' });
|
|
84
|
+
assert(u1 && u2 && u3, 'Inserted 3 users');
|
|
85
|
+
|
|
86
|
+
const p1 = await products.createOne({ name: 'Laptop', price: 999.99, stock: 10 });
|
|
87
|
+
const p2 = await products.createOne({ name: 'Mouse', price: 29.99, stock: 50 });
|
|
88
|
+
const p3 = await products.createOne({ name: 'Keyboard', price: 79.99, stock: 30 });
|
|
89
|
+
assert(p1 && p2 && p3, 'Inserted 3 products');
|
|
90
|
+
|
|
91
|
+
// Get actual IDs from auto-increment
|
|
92
|
+
const allUsers = await users.find({}, { limit: 10 });
|
|
93
|
+
const allProducts = await products.find({}, { limit: 10 });
|
|
94
|
+
const alice = allUsers.find(u => u.email === 'alice@example.com');
|
|
95
|
+
const bob = allUsers.find(u => u.email === 'bob@example.com');
|
|
96
|
+
const laptop = allProducts.find(p => p.name === 'Laptop');
|
|
97
|
+
const mouse = allProducts.find(p => p.name === 'Mouse');
|
|
98
|
+
|
|
99
|
+
assert(alice && bob && laptop && mouse, 'Retrieved inserted records by natural keys');
|
|
100
|
+
|
|
101
|
+
// Create orders
|
|
102
|
+
const o1 = await orders.createOne({ user_id: alice.id, total_amount: 1059.97, status: 'completed' });
|
|
103
|
+
const o2 = await orders.createOne({ user_id: bob.id, total_amount: 29.99, status: 'pending' });
|
|
104
|
+
|
|
105
|
+
const allOrders = await orders.find({}, { limit: 10 });
|
|
106
|
+
const order1 = allOrders.find(o => o.total_amount === 1059.97);
|
|
107
|
+
const order2 = allOrders.find(o => o.total_amount === 29.99);
|
|
108
|
+
|
|
109
|
+
// Order items
|
|
110
|
+
const oi1 = await orderItems.createOne({ order_id: order1.id, product_id: laptop.id, quantity: 1, unit_price: 999.99 });
|
|
111
|
+
const oi2 = await orderItems.createOne({ order_id: order1.id, product_id: mouse.id, quantity: 2, unit_price: 29.99 });
|
|
112
|
+
const oi3 = await orderItems.createOne({ order_id: order2.id, product_id: mouse.id, quantity: 1, unit_price: 29.99 });
|
|
113
|
+
assert(oi1 && oi2 && oi3, 'Inserted 3 order items');
|
|
114
|
+
|
|
115
|
+
// JOIN query via raw SQL
|
|
116
|
+
const joinResult = await EasyPostgresql.Query(`
|
|
117
|
+
SELECT u.name AS user_name, u.email, o.id AS order_id, o.status, o.total_amount
|
|
118
|
+
FROM adv_users u
|
|
119
|
+
INNER JOIN adv_orders o ON u.id = o.user_id
|
|
120
|
+
ORDER BY o.id
|
|
121
|
+
`);
|
|
122
|
+
assert(joinResult.status === 200, 'JOIN query returned success');
|
|
123
|
+
assert(joinResult.data.length === 2, 'JOIN returned 2 rows');
|
|
124
|
+
assert(joinResult.data[0].user_name === alice.name, 'JOIN first row correct user');
|
|
125
|
+
assert(joinResult.data[1].user_name === bob.name, 'JOIN second row correct user');
|
|
126
|
+
|
|
127
|
+
// Three-way JOIN
|
|
128
|
+
const threeWayJoin = await EasyPostgresql.Query(`
|
|
129
|
+
SELECT u.name AS user_name, p.name AS product_name, oi.quantity, oi.unit_price
|
|
130
|
+
FROM adv_order_items oi
|
|
131
|
+
INNER JOIN adv_orders o ON oi.order_id = o.id
|
|
132
|
+
INNER JOIN adv_users u ON o.user_id = u.id
|
|
133
|
+
INNER JOIN adv_products p ON oi.product_id = p.id
|
|
134
|
+
ORDER BY u.name, p.name
|
|
135
|
+
`);
|
|
136
|
+
assert(threeWayJoin.status === 200, '3-way JOIN success');
|
|
137
|
+
assert(threeWayJoin.data.length === 3, '3-way JOIN returned 3 rows');
|
|
138
|
+
|
|
139
|
+
// GROUP BY with aggregation
|
|
140
|
+
const aggResult = await EasyPostgresql.Query(`
|
|
141
|
+
SELECT u.name, COUNT(o.id) AS order_count, COALESCE(SUM(o.total_amount), 0) AS total_spent
|
|
142
|
+
FROM adv_users u
|
|
143
|
+
LEFT JOIN adv_orders o ON u.id = o.user_id
|
|
144
|
+
GROUP BY u.id, u.name
|
|
145
|
+
ORDER BY total_spent DESC
|
|
146
|
+
`);
|
|
147
|
+
assert(aggResult.status === 200, 'GROUP BY query success');
|
|
148
|
+
assert(aggResult.data.length === 3, 'GROUP BY returned 3 rows (all users)');
|
|
149
|
+
const aliceAgg = aggResult.data.find(r => r.name === alice.name);
|
|
150
|
+
assert(aliceAgg && aliceAgg.order_count === '1', 'Alice has 1 order');
|
|
151
|
+
|
|
152
|
+
// Clean up section 1
|
|
153
|
+
await orderItems.functions.remove();
|
|
154
|
+
await orders.functions.remove();
|
|
155
|
+
await products.functions.remove();
|
|
156
|
+
await users.functions.remove();
|
|
157
|
+
console.log(' Section 1 cleanup done'.gray);
|
|
158
|
+
|
|
159
|
+
// ============================================================
|
|
160
|
+
// SECTION 2: Data Type Handling
|
|
161
|
+
// ============================================================
|
|
162
|
+
console.log('\n=== Section 2: Data type handling ==='.cyan);
|
|
163
|
+
|
|
164
|
+
const typeTable = EasyPostgresql.Table('adv_types');
|
|
165
|
+
await typeTable.functions.remove();
|
|
166
|
+
|
|
167
|
+
await typeTable.functions.create({
|
|
168
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey() + EasyPostgresql.Types.options.autoIncrement(),
|
|
169
|
+
bool_col: EasyPostgresql.Types.bit(),
|
|
170
|
+
int_col: EasyPostgresql.Types.int(),
|
|
171
|
+
bigint_col: EasyPostgresql.Types.bigint(),
|
|
172
|
+
float_col: EasyPostgresql.Types.float(),
|
|
173
|
+
decimal_col: EasyPostgresql.Types.decimal(10, 2),
|
|
174
|
+
varchar_col: EasyPostgresql.Types.varchar(50),
|
|
175
|
+
text_col: EasyPostgresql.Types.text(),
|
|
176
|
+
date_col: EasyPostgresql.Types.date(),
|
|
177
|
+
time_col: EasyPostgresql.Types.time(),
|
|
178
|
+
timestamp_col: EasyPostgresql.Types.datetime(),
|
|
179
|
+
char_col: EasyPostgresql.Types.char(),
|
|
180
|
+
uuid_col: EasyPostgresql.Types.uniqueidentifier(),
|
|
181
|
+
money_col: EasyPostgresql.Types.money(),
|
|
182
|
+
bytea_col: EasyPostgresql.Types.image()
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const now = new Date();
|
|
186
|
+
const typeInsert = await typeTable.createOne({
|
|
187
|
+
bool_col: true,
|
|
188
|
+
int_col: 42,
|
|
189
|
+
bigint_col: 9007199254740991,
|
|
190
|
+
float_col: 3.14159,
|
|
191
|
+
decimal_col: 99.99,
|
|
192
|
+
varchar_col: 'Hello World',
|
|
193
|
+
text_col: 'Lorem ipsum dolor sit amet',
|
|
194
|
+
date_col: now,
|
|
195
|
+
time_col: '14:30:00',
|
|
196
|
+
timestamp_col: now,
|
|
197
|
+
char_col: 'A',
|
|
198
|
+
uuid_col: '550e8400-e29b-41d4-a716-446655440000',
|
|
199
|
+
money_col: 1234.56,
|
|
200
|
+
bytea_col: Buffer.from('binary data')
|
|
201
|
+
});
|
|
202
|
+
assert(typeInsert, 'Inserted all data types');
|
|
203
|
+
|
|
204
|
+
const typeResult = await typeTable.findOne({ int_col: 42 });
|
|
205
|
+
assert(typeResult !== null, 'Retrieved typed row');
|
|
206
|
+
assert(typeResult.bool_col === true, 'Boolean type preserved');
|
|
207
|
+
assert(typeResult.int_col === 42, 'Integer type preserved');
|
|
208
|
+
assert(typeResult.float_col === 3.14159, 'Float type preserved');
|
|
209
|
+
assert(typeResult.varchar_col === 'Hello World', 'Varchar type preserved');
|
|
210
|
+
assert(typeResult.text_col === 'Lorem ipsum dolor sit amet', 'Text type preserved');
|
|
211
|
+
assert(typeResult.char_col === 'A', 'Char type preserved');
|
|
212
|
+
assert(typeResult.uuid_col === '550e8400-e29b-41d4-a716-446655440000', 'UUID type preserved');
|
|
213
|
+
|
|
214
|
+
await typeTable.functions.remove();
|
|
215
|
+
console.log(' Section 2 cleanup done'.gray);
|
|
216
|
+
|
|
217
|
+
// ============================================================
|
|
218
|
+
// SECTION 3: Column Operations (Full Lifecycle)
|
|
219
|
+
// ============================================================
|
|
220
|
+
console.log('\n=== Section 3: Column operations ==='.cyan);
|
|
221
|
+
|
|
222
|
+
const colTable = EasyPostgresql.Table('adv_columns');
|
|
223
|
+
await colTable.functions.remove();
|
|
224
|
+
|
|
225
|
+
await colTable.functions.create({
|
|
226
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey(),
|
|
227
|
+
original_name: EasyPostgresql.Types.varchar(50)
|
|
228
|
+
});
|
|
229
|
+
assert(true, 'Table created with original columns');
|
|
230
|
+
|
|
231
|
+
// Add column
|
|
232
|
+
const addCol = await colTable.functions.updateColumn('new_column').add('VARCHAR(100)');
|
|
233
|
+
assert(addCol, 'Added new column');
|
|
234
|
+
|
|
235
|
+
// Verify the column exists
|
|
236
|
+
const infoAfterAdd = await colTable.functions.info();
|
|
237
|
+
assert(infoAfterAdd && infoAfterAdd.schema.new_column !== undefined, 'New column appears in schema');
|
|
238
|
+
|
|
239
|
+
// Rename column
|
|
240
|
+
const renameCol = await colTable.functions.updateColumn('original_name').rename('renamed_name');
|
|
241
|
+
assert(renameCol, 'Renamed column');
|
|
242
|
+
|
|
243
|
+
const infoAfterRename = await colTable.functions.info();
|
|
244
|
+
assert(infoAfterRename && infoAfterRename.schema.renamed_name !== undefined, 'Renamed column appears in schema');
|
|
245
|
+
assert(infoAfterRename && infoAfterRename.schema.original_name === undefined, 'Original name gone from schema');
|
|
246
|
+
|
|
247
|
+
// Insert data with new schema
|
|
248
|
+
const colInsert = await colTable.createOne({ id: 1, renamed_name: 'test', new_column: 'value' });
|
|
249
|
+
assert(colInsert, 'Inserted with renamed and added columns');
|
|
250
|
+
|
|
251
|
+
const colFind = await colTable.findOne({ id: 1 });
|
|
252
|
+
assert(colFind && colFind.renamed_name === 'test', 'Find with renamed column');
|
|
253
|
+
assert(colFind && colFind.new_column === 'value', 'Find with added column');
|
|
254
|
+
|
|
255
|
+
// Drop added column
|
|
256
|
+
const dropCol = await colTable.functions.updateColumn('new_column').remove();
|
|
257
|
+
assert(dropCol, 'Dropped column');
|
|
258
|
+
|
|
259
|
+
const infoAfterDrop = await colTable.functions.info();
|
|
260
|
+
assert(infoAfterDrop && infoAfterDrop.schema.new_column === undefined, 'Dropped column removed from schema');
|
|
261
|
+
|
|
262
|
+
await colTable.functions.remove();
|
|
263
|
+
console.log(' Section 3 cleanup done'.gray);
|
|
264
|
+
|
|
265
|
+
// ============================================================
|
|
266
|
+
// SECTION 4: Error Handling & Edge Cases
|
|
267
|
+
// ============================================================
|
|
268
|
+
console.log('\n=== Section 4: Error handling & edge cases ==='.cyan);
|
|
269
|
+
|
|
270
|
+
// Invalid SQL
|
|
271
|
+
const badQuery = await EasyPostgresql.Query('SELECTT * FROM nonexistent_table');
|
|
272
|
+
assert(badQuery.status === 500, 'Invalid SQL returns status 500');
|
|
273
|
+
assert(badQuery.data === null, 'Invalid SQL returns null data');
|
|
274
|
+
|
|
275
|
+
// Nonexistent table
|
|
276
|
+
const noTable = await EasyPostgresql.Table('adv_nonexistent').find();
|
|
277
|
+
assert(Array.isArray(noTable) && noTable.length === 0, 'Query on nonexistent table returns empty array');
|
|
278
|
+
|
|
279
|
+
// Empty reference
|
|
280
|
+
const emptyRef = await EasyPostgresql.Table('adv_nonexistent').findOne({});
|
|
281
|
+
assert(emptyRef === null, 'findOne with empty ref on nonexistent returns null');
|
|
282
|
+
|
|
283
|
+
// NULL values
|
|
284
|
+
const nullTestTable = EasyPostgresql.Table('adv_null_test');
|
|
285
|
+
await nullTestTable.functions.remove();
|
|
286
|
+
await nullTestTable.functions.create({
|
|
287
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey(),
|
|
288
|
+
nullable_str: EasyPostgresql.Types.varchar(100)
|
|
289
|
+
});
|
|
290
|
+
await nullTestTable.createOne({ id: 1, nullable_str: null });
|
|
291
|
+
const nullFind = await nullTestTable.findOne({ id: 1 });
|
|
292
|
+
assert(nullFind && nullFind.nullable_str === null, 'NULL value handled correctly');
|
|
293
|
+
|
|
294
|
+
await nullTestTable.functions.remove();
|
|
295
|
+
|
|
296
|
+
// Duplicate unique constraint
|
|
297
|
+
const uniqueTable = EasyPostgresql.Table('adv_unique_test');
|
|
298
|
+
await uniqueTable.functions.remove();
|
|
299
|
+
await uniqueTable.functions.create({
|
|
300
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey(),
|
|
301
|
+
code: EasyPostgresql.Types.varchar(20) + EasyPostgresql.Types.options.unique()
|
|
302
|
+
});
|
|
303
|
+
await uniqueTable.createOne({ id: 1, code: 'UNIQUE1' });
|
|
304
|
+
const dupResult = await uniqueTable.createOne({ id: 2, code: 'UNIQUE1' });
|
|
305
|
+
assert(dupResult === false, 'Duplicate unique value returns false');
|
|
306
|
+
|
|
307
|
+
await uniqueTable.functions.remove();
|
|
308
|
+
console.log(' Section 4 cleanup done'.gray);
|
|
309
|
+
|
|
310
|
+
// ============================================================
|
|
311
|
+
// SECTION 5: Bulk Operations & Complex Filtering
|
|
312
|
+
// ============================================================
|
|
313
|
+
console.log('\n=== Section 5: Bulk operations & filtering ==='.cyan);
|
|
314
|
+
|
|
315
|
+
const bulkTable = EasyPostgresql.Table('adv_bulk');
|
|
316
|
+
await bulkTable.functions.remove();
|
|
317
|
+
await bulkTable.functions.create({
|
|
318
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey(),
|
|
319
|
+
category: EasyPostgresql.Types.varchar(50),
|
|
320
|
+
score: EasyPostgresql.Types.int(),
|
|
321
|
+
active: EasyPostgresql.Types.bit()
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Bulk insert
|
|
325
|
+
const bulkData = [
|
|
326
|
+
{ id: 1, category: 'A', score: 85, active: true },
|
|
327
|
+
{ id: 2, category: 'A', score: 92, active: true },
|
|
328
|
+
{ id: 3, category: 'B', score: 78, active: false },
|
|
329
|
+
{ id: 4, category: 'B', score: 95, active: true },
|
|
330
|
+
{ id: 5, category: 'A', score: 60, active: false },
|
|
331
|
+
{ id: 6, category: 'C', score: 88, active: true },
|
|
332
|
+
{ id: 7, category: 'C', score: 73, active: true },
|
|
333
|
+
{ id: 8, category: 'B', score: 81, active: false },
|
|
334
|
+
{ id: 9, category: 'A', score: 99, active: true },
|
|
335
|
+
{ id: 10, category: 'C', score: 67, active: false }
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
let bulkInserts = 0;
|
|
339
|
+
for (const row of bulkData) {
|
|
340
|
+
if (await bulkTable.createOne(row)) bulkInserts++;
|
|
341
|
+
}
|
|
342
|
+
assert(bulkInserts === 10, 'Bulk inserted 10 records');
|
|
343
|
+
|
|
344
|
+
// Complex filter: category = A AND active = true
|
|
345
|
+
const catA_active = await bulkTable.find({ category: 'A', active: true });
|
|
346
|
+
assert(catA_active.length === 3, 'Complex AND filter: category A + active');
|
|
347
|
+
|
|
348
|
+
// LIKE filter
|
|
349
|
+
const likeFilter = await bulkTable.find({}, { likes: { category: 'A?' } });
|
|
350
|
+
assert(likeFilter.length === 4, 'LIKE filter with ? placeholder');
|
|
351
|
+
|
|
352
|
+
// selected_keys
|
|
353
|
+
const selected = await bulkTable.find({ category: 'B' }, { selected_keys: ['id', 'score'] });
|
|
354
|
+
assert(selected.length === 3, 'selected_keys filter correct count');
|
|
355
|
+
assert(selected[0].category === undefined, 'selected_keys excludes non-selected columns');
|
|
356
|
+
|
|
357
|
+
// Limit
|
|
358
|
+
const limited = await bulkTable.find({}, { limit: 3 });
|
|
359
|
+
assert(limited.length === 3, 'LIMIT works correctly');
|
|
360
|
+
|
|
361
|
+
// Ordering via raw query
|
|
362
|
+
const ordered = await EasyPostgresql.Query('SELECT * FROM adv_bulk ORDER BY score DESC');
|
|
363
|
+
assert(ordered.data.length === 10, 'ORDER BY returns all rows');
|
|
364
|
+
assert(ordered.data[0].score === 99, 'ORDER BY DESC first is highest score');
|
|
365
|
+
assert(ordered.data[9].score === 60, 'ORDER BY DESC last is lowest score');
|
|
366
|
+
|
|
367
|
+
// Subquery
|
|
368
|
+
const subquery = await EasyPostgresql.Query(`
|
|
369
|
+
SELECT category, AVG(score)::numeric(10,1) AS avg_score
|
|
370
|
+
FROM adv_bulk
|
|
371
|
+
GROUP BY category
|
|
372
|
+
HAVING AVG(score) > 75
|
|
373
|
+
ORDER BY avg_score DESC
|
|
374
|
+
`);
|
|
375
|
+
assert(subquery.status === 200, 'Subquery with HAVING success');
|
|
376
|
+
assert(subquery.data.length >= 1, 'Subquery returned results');
|
|
377
|
+
|
|
378
|
+
// Multiple deleteAll
|
|
379
|
+
const deleteB_all = await bulkTable.deleteAll({ category: 'B' });
|
|
380
|
+
assert(deleteB_all, 'deleteAll for category B');
|
|
381
|
+
const remainingB = await bulkTable.find({ category: 'B' });
|
|
382
|
+
assert(remainingB.length === 0, 'No B records remaining');
|
|
383
|
+
|
|
384
|
+
const deleteAll = await bulkTable.deleteAll();
|
|
385
|
+
assert(deleteAll, 'deleteAll all remaining');
|
|
386
|
+
const allGone = await bulkTable.find();
|
|
387
|
+
assert(allGone.length === 0, 'All records gone');
|
|
388
|
+
|
|
389
|
+
await bulkTable.functions.remove();
|
|
390
|
+
console.log(' Section 5 cleanup done'.gray);
|
|
391
|
+
|
|
392
|
+
// ============================================================
|
|
393
|
+
// SECTION 6: Update Operations & Conditional Logic
|
|
394
|
+
// ============================================================
|
|
395
|
+
console.log('\n=== Section 6: Update operations ==='.cyan);
|
|
396
|
+
|
|
397
|
+
const updateTable = EasyPostgresql.Table('adv_updates');
|
|
398
|
+
await updateTable.functions.remove();
|
|
399
|
+
await updateTable.functions.create({
|
|
400
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey(),
|
|
401
|
+
status: EasyPostgresql.Types.varchar(20) + ' ' + "DEFAULT 'pending'",
|
|
402
|
+
counter: EasyPostgresql.Types.int() + ' ' + 'DEFAULT 0',
|
|
403
|
+
label: EasyPostgresql.Types.varchar(50)
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await updateTable.createOne({ id: 1, status: 'active', counter: 5, label: 'Test A' });
|
|
407
|
+
await updateTable.createOne({ id: 2, status: 'pending', counter: 0, label: 'Test B' });
|
|
408
|
+
await updateTable.createOne({ id: 3, status: 'active', counter: 10, label: 'Test C' });
|
|
409
|
+
|
|
410
|
+
// Update single field
|
|
411
|
+
const up1 = await updateTable.updateOne({ id: 1 }, { status: 'completed' });
|
|
412
|
+
assert(up1, 'Update single field');
|
|
413
|
+
const afterUp1 = await updateTable.findOne({ id: 1 });
|
|
414
|
+
assert(afterUp1.status === 'completed', 'Updated field has new value');
|
|
415
|
+
|
|
416
|
+
// Update multiple fields
|
|
417
|
+
const up2 = await updateTable.updateOne({ id: 2 }, { status: 'active', counter: 15 });
|
|
418
|
+
assert(up2, 'Update multiple fields');
|
|
419
|
+
const afterUp2 = await updateTable.findOne({ id: 2 });
|
|
420
|
+
assert(afterUp2.status === 'active' && afterUp2.counter === 15, 'Both fields updated');
|
|
421
|
+
|
|
422
|
+
// Update non-existing row
|
|
423
|
+
const up3 = await updateTable.updateOne({ id: 999 }, { status: 'deleted' });
|
|
424
|
+
assert(up3 === false, 'Update non-existing row returns false');
|
|
425
|
+
|
|
426
|
+
// Count after updates
|
|
427
|
+
const activeCount = await updateTable.find({ status: 'active' });
|
|
428
|
+
assert(activeCount.length === 2, 'Active count after updates is 2');
|
|
429
|
+
const completedCount = await updateTable.find({ status: 'completed' });
|
|
430
|
+
assert(completedCount.length === 1, 'Completed count after updates is 1');
|
|
431
|
+
|
|
432
|
+
await updateTable.functions.remove();
|
|
433
|
+
console.log(' Section 6 cleanup done'.gray);
|
|
434
|
+
|
|
435
|
+
// ============================================================
|
|
436
|
+
// SECTION 7: Aggregation Functions
|
|
437
|
+
// ============================================================
|
|
438
|
+
console.log('\n=== Section 7: Aggregation functions ==='.cyan);
|
|
439
|
+
|
|
440
|
+
const aggTable = EasyPostgresql.Table('adv_agg');
|
|
441
|
+
await aggTable.functions.remove();
|
|
442
|
+
await aggTable.functions.create({
|
|
443
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey(),
|
|
444
|
+
value: EasyPostgresql.Types.int()
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
for (let i = 0; i < 5; i++) {
|
|
448
|
+
await aggTable.createOne({ id: i + 1, value: (i + 1) * 10 });
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const aggCount = await aggTable.count();
|
|
452
|
+
assert(aggCount === 5, 'count returns 5');
|
|
453
|
+
|
|
454
|
+
const aggMin = await aggTable.min('value');
|
|
455
|
+
assert(aggMin === 10, 'min returns 10');
|
|
456
|
+
|
|
457
|
+
const aggMax = await aggTable.max('value');
|
|
458
|
+
assert(aggMax === 50, 'max returns 50');
|
|
459
|
+
|
|
460
|
+
const aggAvg = await aggTable.average('value');
|
|
461
|
+
assert(aggAvg === 30, 'average returns 30');
|
|
462
|
+
|
|
463
|
+
const aggLast = await aggTable.last();
|
|
464
|
+
assert(aggLast && aggLast.id === 5, 'last returns row with id 5');
|
|
465
|
+
|
|
466
|
+
const aggFirst = await aggTable.first();
|
|
467
|
+
assert(aggFirst && aggFirst.id === 1, 'first returns row with id 1');
|
|
468
|
+
|
|
469
|
+
// Single - exactly one match
|
|
470
|
+
const aggSingle = await aggTable.single({ id: 3 });
|
|
471
|
+
assert(aggSingle && aggSingle.value === 30, 'single returns exact match');
|
|
472
|
+
|
|
473
|
+
// Single - throws on multiple matches
|
|
474
|
+
try {
|
|
475
|
+
await aggTable.single({});
|
|
476
|
+
assert(false, 'single should throw on multiple matches');
|
|
477
|
+
} catch (e) {
|
|
478
|
+
assert(e.message.includes('more than one'), 'single throws on multiple matches');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// SingleOrDefault - no match
|
|
482
|
+
const aggSingleDef = await aggTable.singleOrDefault({ id: 999 });
|
|
483
|
+
assert(aggSingleDef === null, 'singleOrDefault returns null for no match');
|
|
484
|
+
|
|
485
|
+
await aggTable.functions.remove();
|
|
486
|
+
console.log(' Section 7 cleanup done'.gray);
|
|
487
|
+
|
|
488
|
+
// ============================================================
|
|
489
|
+
// SECTION 8: Table Management (getAll, isThere)
|
|
490
|
+
// ============================================================
|
|
491
|
+
console.log('\n=== Section 8: Table management ==='.cyan);
|
|
492
|
+
|
|
493
|
+
const mgmtTable1 = EasyPostgresql.Table('adv_mgmt_a');
|
|
494
|
+
const mgmtTable2 = EasyPostgresql.Table('adv_mgmt_b');
|
|
495
|
+
|
|
496
|
+
await mgmtTable1.functions.remove();
|
|
497
|
+
await mgmtTable2.functions.remove();
|
|
498
|
+
|
|
499
|
+
await mgmtTable1.functions.create({
|
|
500
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey()
|
|
501
|
+
});
|
|
502
|
+
await mgmtTable2.functions.create({
|
|
503
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey()
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const isThere1 = await mgmtTable1.functions.isThere();
|
|
507
|
+
const isThere2 = await mgmtTable2.functions.isThere();
|
|
508
|
+
assert(isThere1 && isThere2, 'isThere returns true for existing tables');
|
|
509
|
+
|
|
510
|
+
// getAll
|
|
511
|
+
const allTableInfos = await mgmtTable1.functions.getAll();
|
|
512
|
+
assert(Array.isArray(allTableInfos), 'getAll returns array');
|
|
513
|
+
assert(allTableInfos.some(t => t.name === 'adv_mgmt_a'), 'getAll includes adv_mgmt_a');
|
|
514
|
+
assert(allTableInfos.some(t => t.name === 'adv_mgmt_b'), 'getAll includes adv_mgmt_b');
|
|
515
|
+
|
|
516
|
+
// info
|
|
517
|
+
const info1 = await mgmtTable1.functions.info();
|
|
518
|
+
assert(info1 && info1.name === 'adv_mgmt_a', 'info returns correct name');
|
|
519
|
+
assert(info1.schema && info1.schema.id !== undefined, 'info includes schema');
|
|
520
|
+
|
|
521
|
+
await mgmtTable1.functions.remove();
|
|
522
|
+
await mgmtTable2.functions.remove();
|
|
523
|
+
|
|
524
|
+
const isThereAfter = await mgmtTable1.functions.isThere();
|
|
525
|
+
assert(isThereAfter === false, 'isThere returns false after remove');
|
|
526
|
+
|
|
527
|
+
console.log(' Section 8 cleanup done'.gray);
|
|
528
|
+
|
|
529
|
+
// ============================================================
|
|
530
|
+
// SECTION 9: Concurrent Operations
|
|
531
|
+
// ============================================================
|
|
532
|
+
console.log('\n=== Section 9: Concurrent operations ==='.cyan);
|
|
533
|
+
|
|
534
|
+
const concTable = EasyPostgresql.Table('adv_concurrent');
|
|
535
|
+
await concTable.functions.remove();
|
|
536
|
+
await concTable.functions.create({
|
|
537
|
+
id: EasyPostgresql.Types.int() + EasyPostgresql.Types.options.primaryKey(),
|
|
538
|
+
val: EasyPostgresql.Types.int()
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const concurrentOps = [];
|
|
542
|
+
for (let i = 0; i < 10; i++) {
|
|
543
|
+
concurrentOps.push(concTable.createOne({ id: i + 1, val: i * 10 }));
|
|
544
|
+
}
|
|
545
|
+
const concResults = await Promise.all(concurrentOps);
|
|
546
|
+
const allSuccess = concResults.every(r => r === true);
|
|
547
|
+
assert(allSuccess, '10 concurrent inserts all succeed');
|
|
548
|
+
|
|
549
|
+
const concCount = await concTable.count();
|
|
550
|
+
assert(concCount === 10, 'Concurrent inserts count matches');
|
|
551
|
+
|
|
552
|
+
await concTable.functions.remove();
|
|
553
|
+
console.log(' Section 9 cleanup done'.gray);
|
|
554
|
+
|
|
555
|
+
// ============================================================
|
|
556
|
+
// SECTION 10: Stored Function with Complex Logic
|
|
557
|
+
// ============================================================
|
|
558
|
+
console.log('\n=== Section 10: Advanced stored function ==='.cyan);
|
|
559
|
+
|
|
560
|
+
await EasyPostgresql.Query(`
|
|
561
|
+
CREATE OR REPLACE FUNCTION adv_calculate_discount(
|
|
562
|
+
p_amount NUMERIC,
|
|
563
|
+
p_customer_type VARCHAR
|
|
564
|
+
)
|
|
565
|
+
RETURNS NUMERIC AS $$
|
|
566
|
+
DECLARE
|
|
567
|
+
discount NUMERIC;
|
|
568
|
+
BEGIN
|
|
569
|
+
IF p_customer_type = 'VIP' THEN
|
|
570
|
+
discount := p_amount * 0.20;
|
|
571
|
+
ELSIF p_customer_type = 'REGULAR' THEN
|
|
572
|
+
discount := p_amount * 0.10;
|
|
573
|
+
ELSIF p_amount > 1000 THEN
|
|
574
|
+
discount := p_amount * 0.05;
|
|
575
|
+
ELSE
|
|
576
|
+
discount := 0;
|
|
577
|
+
END IF;
|
|
578
|
+
RETURN ROUND(discount, 2);
|
|
579
|
+
END;
|
|
580
|
+
$$ LANGUAGE plpgsql;
|
|
581
|
+
`);
|
|
582
|
+
|
|
583
|
+
const funcVIP = await EasyPostgresql.Procedure('adv_calculate_discount').Execute({
|
|
584
|
+
p_amount: 500,
|
|
585
|
+
p_customer_type: 'VIP'
|
|
586
|
+
});
|
|
587
|
+
assert(funcVIP.status === 200, 'VIP discount function executed');
|
|
588
|
+
|
|
589
|
+
const funcRegular = await EasyPostgresql.Procedure('adv_calculate_discount').Execute({
|
|
590
|
+
p_amount: 500,
|
|
591
|
+
p_customer_type: 'REGULAR'
|
|
592
|
+
});
|
|
593
|
+
assert(funcRegular.status === 200, 'Regular discount function executed');
|
|
594
|
+
|
|
595
|
+
const funcHigh = await EasyPostgresql.Procedure('adv_calculate_discount').Execute({
|
|
596
|
+
p_amount: 2000,
|
|
597
|
+
p_customer_type: 'BASIC'
|
|
598
|
+
});
|
|
599
|
+
assert(funcHigh.status === 200, 'High amount discount function executed');
|
|
600
|
+
|
|
601
|
+
const funcNone = await EasyPostgresql.Procedure('adv_calculate_discount').Execute({
|
|
602
|
+
p_amount: 100,
|
|
603
|
+
p_customer_type: 'BASIC'
|
|
604
|
+
});
|
|
605
|
+
assert(funcNone.status === 200, 'No discount function executed');
|
|
606
|
+
|
|
607
|
+
// Function returning TABLE
|
|
608
|
+
await EasyPostgresql.Query('CREATE OR REPLACE FUNCTION adv_top_customers(p_limit INT) RETURNS TABLE(customer_name VARCHAR, total_spent NUMERIC) AS ' + '$$' + ' BEGIN RETURN QUERY SELECT s::VARCHAR, s::NUMERIC FROM generate_series(1, p_limit) AS s; END; ' + '$$' + ' LANGUAGE plpgsql;');
|
|
609
|
+
|
|
610
|
+
const topCust = await EasyPostgresql.Procedure('adv_top_customers').Execute({ p_limit: 5 });
|
|
611
|
+
assert(topCust.status === 200, 'TABLE-returning function executed');
|
|
612
|
+
assert(topCust.data.length === 5, 'TABLE-returning function returned 5 rows');
|
|
613
|
+
|
|
614
|
+
await EasyPostgresql.Query('DROP FUNCTION IF EXISTS adv_calculate_discount');
|
|
615
|
+
await EasyPostgresql.Query('DROP FUNCTION IF EXISTS adv_top_customers');
|
|
616
|
+
console.log(' Section 10 cleanup done'.gray);
|
|
617
|
+
|
|
618
|
+
// ============================================================
|
|
619
|
+
// FINAL RESULTS
|
|
620
|
+
// ============================================================
|
|
621
|
+
console.log('\n' + '='.repeat(50));
|
|
622
|
+
console.log(` Total: ${passed + failed} | Passed: ${passed}`.green + ` | Failed: ${failed}`.red);
|
|
623
|
+
console.log('='.repeat(50) + '\n');
|
|
624
|
+
|
|
625
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
run().catch(e => {
|
|
629
|
+
console.error('FATAL ERROR:', e);
|
|
630
|
+
process.exit(1);
|
|
631
|
+
});
|