@onurege3467/zerohelper 6.1.0 → 7.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/.snapshots/config.json +151 -0
- package/.snapshots/readme.md +11 -0
- package/.snapshots/sponsors.md +44 -0
- package/data/test_db.json +3 -0
- package/data/test_db.sqlite +0 -0
- package/data/test_db_cached.sqlite +0 -0
- package/database/index.js +2 -0
- package/database/mysql.js +140 -15
- package/database/pg.js +473 -0
- package/database/sqlite.js +111 -7
- package/package.json +1 -1
- package/readme.md +11 -0
- package/test copy.js +144 -0
- package/test.js +14 -107
package/database/pg.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
const IDatabase = require('./IDatabase'); // Arayüzü import et
|
|
2
|
+
const { Pool } = require('pg');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @implements {IDatabase}
|
|
6
|
+
*/
|
|
7
|
+
class PostgreSQLDatabase extends IDatabase {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
super();
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.pool = null;
|
|
12
|
+
this._queue = [];
|
|
13
|
+
this._connected = false;
|
|
14
|
+
|
|
15
|
+
this._connectionPromise = new Promise(async (resolve, reject) => {
|
|
16
|
+
try {
|
|
17
|
+
// First, connect to create database if not exists
|
|
18
|
+
const tempPool = new Pool({
|
|
19
|
+
host: config.host,
|
|
20
|
+
port: config.port || 5432,
|
|
21
|
+
user: config.user,
|
|
22
|
+
password: config.password,
|
|
23
|
+
database: 'postgres' // Connect to default database first
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await tempPool.query(`CREATE DATABASE "${config.database}"`);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// Database might already exist, ignore error
|
|
30
|
+
if (!error.message.includes('already exists')) {
|
|
31
|
+
console.warn('Database creation warning:', error.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await tempPool.end();
|
|
35
|
+
|
|
36
|
+
// Now connect to the actual database
|
|
37
|
+
this.pool = new Pool({
|
|
38
|
+
host: config.host,
|
|
39
|
+
port: config.port || 5432,
|
|
40
|
+
user: config.user,
|
|
41
|
+
password: config.password,
|
|
42
|
+
database: config.database,
|
|
43
|
+
max: config.connectionLimit || 10,
|
|
44
|
+
idleTimeoutMillis: 30000,
|
|
45
|
+
connectionTimeoutMillis: 2000,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this._connected = true;
|
|
49
|
+
resolve(this.pool);
|
|
50
|
+
this._processQueue(); // Process any queued requests
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("PostgreSQL connection error:", error);
|
|
53
|
+
reject(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Değerin türüne göre PostgreSQL column türünü otomatik belirler
|
|
60
|
+
*/
|
|
61
|
+
_getColumnType(value) {
|
|
62
|
+
if (value === null || value === undefined) {
|
|
63
|
+
return 'TEXT'; // Null değerler için varsayılan
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof value === 'boolean') {
|
|
67
|
+
return 'BOOLEAN';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (typeof value === 'number') {
|
|
71
|
+
if (Number.isInteger(value)) {
|
|
72
|
+
// Integer range kontrolü
|
|
73
|
+
if (value >= -32768 && value <= 32767) {
|
|
74
|
+
return 'SMALLINT';
|
|
75
|
+
} else if (value >= -2147483648 && value <= 2147483647) {
|
|
76
|
+
return 'INTEGER';
|
|
77
|
+
} else {
|
|
78
|
+
return 'BIGINT';
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Float/Double için
|
|
82
|
+
return 'DOUBLE PRECISION';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof value === 'string') {
|
|
87
|
+
const length = value.length;
|
|
88
|
+
if (length <= 255) {
|
|
89
|
+
return 'VARCHAR(255)';
|
|
90
|
+
} else {
|
|
91
|
+
return 'TEXT';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof value === 'object') {
|
|
96
|
+
// Array ve Object'ler JSONB olarak saklanır
|
|
97
|
+
return 'JSONB';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (value instanceof Date) {
|
|
101
|
+
return 'TIMESTAMP';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Varsayılan
|
|
105
|
+
return 'TEXT';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Birden fazla değere göre en uygun column türünü belirler
|
|
110
|
+
*/
|
|
111
|
+
_getBestColumnType(values) {
|
|
112
|
+
const types = values.map(val => this._getColumnType(val));
|
|
113
|
+
const uniqueTypes = [...new Set(types)];
|
|
114
|
+
|
|
115
|
+
// Eğer hepsi aynı türse, o türü kullan
|
|
116
|
+
if (uniqueTypes.length === 1) {
|
|
117
|
+
return uniqueTypes[0];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Mixed türler için öncelik sırası
|
|
121
|
+
const typePriority = {
|
|
122
|
+
'TEXT': 10,
|
|
123
|
+
'JSONB': 9,
|
|
124
|
+
'VARCHAR(255)': 8,
|
|
125
|
+
'TIMESTAMP': 7,
|
|
126
|
+
'DOUBLE PRECISION': 6,
|
|
127
|
+
'BIGINT': 5,
|
|
128
|
+
'INTEGER': 4,
|
|
129
|
+
'SMALLINT': 3,
|
|
130
|
+
'BOOLEAN': 2
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// En yüksek öncelikli türü seç
|
|
134
|
+
let bestType = uniqueTypes[0];
|
|
135
|
+
let bestPriority = typePriority[bestType] || 0;
|
|
136
|
+
|
|
137
|
+
for (const type of uniqueTypes) {
|
|
138
|
+
const priority = typePriority[type] || 0;
|
|
139
|
+
if (priority > bestPriority) {
|
|
140
|
+
bestType = type;
|
|
141
|
+
bestPriority = priority;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return bestType;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async _queueRequest(operation) {
|
|
149
|
+
if (this._connected) {
|
|
150
|
+
return operation();
|
|
151
|
+
} else {
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
this._queue.push({ operation, resolve, reject });
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async _processQueue() {
|
|
159
|
+
if (!this._connected) return;
|
|
160
|
+
|
|
161
|
+
while (this._queue.length > 0) {
|
|
162
|
+
const { operation, resolve, reject } = this._queue.shift();
|
|
163
|
+
try {
|
|
164
|
+
const result = await operation();
|
|
165
|
+
resolve(result);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
reject(error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async query(sql, params = []) {
|
|
173
|
+
return this._queueRequest(async () => {
|
|
174
|
+
const pool = await this._connectionPromise;
|
|
175
|
+
const result = await pool.query(sql, params);
|
|
176
|
+
return result.rows;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async ensureTable(table, data = {}) {
|
|
181
|
+
return this._queueRequest(async () => {
|
|
182
|
+
const tables = await this.query(`
|
|
183
|
+
SELECT table_name
|
|
184
|
+
FROM information_schema.tables
|
|
185
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
186
|
+
`, [table]);
|
|
187
|
+
|
|
188
|
+
if (tables.length === 0) {
|
|
189
|
+
const columnDefinitions = Object.keys(data).map(col => {
|
|
190
|
+
const columnType = this._getColumnType(data[col]);
|
|
191
|
+
return `"${col}" ${columnType}`;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
let columnsPart = '';
|
|
195
|
+
if (columnDefinitions.length > 0) {
|
|
196
|
+
columnsPart = ', ' + columnDefinitions.join(", ");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const createTableSQL = `
|
|
200
|
+
CREATE TABLE "${table}" (
|
|
201
|
+
"_id" SERIAL PRIMARY KEY
|
|
202
|
+
${columnsPart}
|
|
203
|
+
)
|
|
204
|
+
`;
|
|
205
|
+
await this.query(createTableSQL);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async insert(table, data) {
|
|
211
|
+
return this._queueRequest(async () => {
|
|
212
|
+
const copy = { ...data };
|
|
213
|
+
await this.ensureTable(table, copy);
|
|
214
|
+
|
|
215
|
+
const existingColumns = await this.query(`
|
|
216
|
+
SELECT column_name, column_default, is_nullable, data_type
|
|
217
|
+
FROM information_schema.columns
|
|
218
|
+
WHERE table_name = $1 AND table_schema = 'public'
|
|
219
|
+
`, [table]);
|
|
220
|
+
|
|
221
|
+
if (!existingColumns || existingColumns.length === 0) {
|
|
222
|
+
throw new Error(`Table ${table} does not exist.`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const existingNames = existingColumns.map(col => col.column_name);
|
|
226
|
+
const primaryKeyColumn = existingColumns.find(col =>
|
|
227
|
+
col.column_default && col.column_default.includes('nextval')
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const insertData = { ...copy };
|
|
231
|
+
// Remove the auto-incrementing primary key from insertData if it's present
|
|
232
|
+
if (primaryKeyColumn && insertData[primaryKeyColumn.column_name] !== undefined) {
|
|
233
|
+
delete insertData[primaryKeyColumn.column_name];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const key of Object.keys(insertData)) {
|
|
237
|
+
if (!existingNames.includes(key)) {
|
|
238
|
+
const columnType = this._getColumnType(insertData[key]);
|
|
239
|
+
await this.query(`ALTER TABLE "${table}" ADD COLUMN "${key}" ${columnType}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const keys = Object.keys(insertData);
|
|
244
|
+
const placeholders = keys.map((_, index) => `$${index + 1}`).join(",");
|
|
245
|
+
const values = Object.values(insertData).map(value => this._serializeValue(value));
|
|
246
|
+
const sql = `INSERT INTO "${table}" (${keys.map(k => `"${k}"`).join(",")}) VALUES (${placeholders}) RETURNING "_id"`;
|
|
247
|
+
|
|
248
|
+
const result = await this.query(sql, values);
|
|
249
|
+
return result[0]._id;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async update(table, data, where) {
|
|
254
|
+
return this._queueRequest(async () => {
|
|
255
|
+
await this.ensureTable(table, { ...data, ...where });
|
|
256
|
+
|
|
257
|
+
const existingColumns = await this.query(`
|
|
258
|
+
SELECT column_name
|
|
259
|
+
FROM information_schema.columns
|
|
260
|
+
WHERE table_name = $1 AND table_schema = 'public'
|
|
261
|
+
`, [table]);
|
|
262
|
+
|
|
263
|
+
if (!existingColumns || existingColumns.length === 0) {
|
|
264
|
+
throw new Error(`Table ${table} does not exist.`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const existingColumnNames = existingColumns.map(col => col.column_name);
|
|
268
|
+
for (const key of Object.keys(data)) {
|
|
269
|
+
if (!existingColumnNames.includes(key)) {
|
|
270
|
+
const columnType = this._getColumnType(data[key]);
|
|
271
|
+
const alterSQL = `ALTER TABLE "${table}" ADD COLUMN "${key}" ${columnType}`;
|
|
272
|
+
await this.query(alterSQL);
|
|
273
|
+
console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const setString = Object.keys(data).map((k, i) => `"${k}" = $${i + 1}`).join(", ");
|
|
278
|
+
const whereString = Object.keys(where).map((k, i) => `"${k}" = $${Object.keys(data).length + i + 1}`).join(" AND ");
|
|
279
|
+
const sql = `UPDATE "${table}" SET ${setString} WHERE ${whereString}`;
|
|
280
|
+
|
|
281
|
+
const result = await this.query(sql, [...Object.values(data).map(v => this._serializeValue(v)), ...Object.values(where)]);
|
|
282
|
+
return result.length; // PostgreSQL doesn't return affectedRows in the same way
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async delete(table, where) {
|
|
287
|
+
return this._queueRequest(async () => {
|
|
288
|
+
if (!where || Object.keys(where).length === 0) return 0;
|
|
289
|
+
await this.ensureTable(table, { ...where });
|
|
290
|
+
|
|
291
|
+
const whereString = Object.keys(where).map((k, i) => `"${k}" = $${i + 1}`).join(" AND ");
|
|
292
|
+
const sql = `DELETE FROM "${table}" WHERE ${whereString}`;
|
|
293
|
+
const result = await this.query(sql, Object.values(where));
|
|
294
|
+
return result.length;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async select(table, where = null) {
|
|
299
|
+
return this._queueRequest(async () => {
|
|
300
|
+
await this.ensureTable(table, where || {});
|
|
301
|
+
let sql = `SELECT * FROM "${table}"`;
|
|
302
|
+
let params = [];
|
|
303
|
+
|
|
304
|
+
if (where && Object.keys(where).length > 0) {
|
|
305
|
+
const whereString = Object.keys(where).map((k, i) => `"${k}" = $${i + 1}`).join(" AND ");
|
|
306
|
+
sql += ` WHERE ${whereString}`;
|
|
307
|
+
params = Object.values(where);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const rows = await this.query(sql, params);
|
|
311
|
+
return rows.map(row => {
|
|
312
|
+
const newRow = {};
|
|
313
|
+
for (const key in row) {
|
|
314
|
+
newRow[key] = this._deserializeValue(row[key]);
|
|
315
|
+
}
|
|
316
|
+
return newRow;
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async set(table, data, where) {
|
|
322
|
+
return this._queueRequest(async () => {
|
|
323
|
+
await this.ensureTable(table, { ...data, ...where });
|
|
324
|
+
|
|
325
|
+
const existingColumns = await this.query(`
|
|
326
|
+
SELECT column_name
|
|
327
|
+
FROM information_schema.columns
|
|
328
|
+
WHERE table_name = $1 AND table_schema = 'public'
|
|
329
|
+
`, [table]);
|
|
330
|
+
|
|
331
|
+
if (!existingColumns || existingColumns.length === 0) {
|
|
332
|
+
throw new Error(`Table ${table} does not exist.`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const existingColumnNames = existingColumns.map(col => col.column_name);
|
|
336
|
+
for (const key of Object.keys(data)) {
|
|
337
|
+
if (!existingColumnNames.includes(key)) {
|
|
338
|
+
const columnType = this._getColumnType(data[key]);
|
|
339
|
+
const alterSQL = `ALTER TABLE "${table}" ADD COLUMN "${key}" ${columnType}`;
|
|
340
|
+
await this.query(alterSQL);
|
|
341
|
+
console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const existing = await this.select(table, where);
|
|
346
|
+
if (existing.length === 0) {
|
|
347
|
+
return await this.insert(table, { ...where, ...data });
|
|
348
|
+
} else {
|
|
349
|
+
return await this.update(table, data, where);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async selectOne(table, where = null) {
|
|
355
|
+
return this._queueRequest(async () => {
|
|
356
|
+
const results = await this.select(table, where);
|
|
357
|
+
if (results[0]) {
|
|
358
|
+
const newResult = {};
|
|
359
|
+
for (const key in results[0]) {
|
|
360
|
+
newResult[key] = this._deserializeValue(results[0][key]);
|
|
361
|
+
}
|
|
362
|
+
return newResult;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async deleteOne(table, where) {
|
|
369
|
+
return this._queueRequest(async () => {
|
|
370
|
+
const row = await this.selectOne(table, where);
|
|
371
|
+
if (!row) return 0;
|
|
372
|
+
|
|
373
|
+
const whereString = Object.keys(where).map((k, i) => `"${k}" = $${i + 1}`).join(" AND ");
|
|
374
|
+
const sql = `DELETE FROM "${table}" WHERE ${whereString} AND ctid = (SELECT ctid FROM "${table}" WHERE ${whereString} LIMIT 1)`;
|
|
375
|
+
const result = await this.query(sql, [...Object.values(where), ...Object.values(where)]);
|
|
376
|
+
return result.length;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async updateOne(table, data, where) {
|
|
381
|
+
return this._queueRequest(async () => {
|
|
382
|
+
await this.ensureTable(table, { ...data, ...where });
|
|
383
|
+
|
|
384
|
+
const existingColumns = await this.query(`
|
|
385
|
+
SELECT column_name
|
|
386
|
+
FROM information_schema.columns
|
|
387
|
+
WHERE table_name = $1 AND table_schema = 'public'
|
|
388
|
+
`, [table]);
|
|
389
|
+
|
|
390
|
+
if (!existingColumns || existingColumns.length === 0) {
|
|
391
|
+
throw new Error(`Table ${table} does not exist.`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const existingColumnNames = existingColumns.map(col => col.column_name);
|
|
395
|
+
for (const key of Object.keys(data)) {
|
|
396
|
+
if (!existingColumnNames.includes(key)) {
|
|
397
|
+
const columnType = this._getColumnType(data[key]);
|
|
398
|
+
const alterSQL = `ALTER TABLE "${table}" ADD COLUMN "${key}" ${columnType}`;
|
|
399
|
+
await this.query(alterSQL);
|
|
400
|
+
console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const setString = Object.keys(data).map((k, i) => `"${k}" = $${i + 1}`).join(", ");
|
|
405
|
+
const whereString = Object.keys(where).map((k, i) => `"${k}" = $${Object.keys(data).length + i + 1}`).join(" AND ");
|
|
406
|
+
const sql = `UPDATE "${table}" SET ${setString} WHERE ${whereString} AND ctid = (SELECT ctid FROM "${table}" WHERE ${whereString} LIMIT 1)`;
|
|
407
|
+
|
|
408
|
+
const result = await this.query(sql, [...Object.values(data).map(v => this._serializeValue(v)), ...Object.values(where), ...Object.values(where)]);
|
|
409
|
+
return result.length;
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async bulkInsert(table, dataArray) {
|
|
414
|
+
return this._queueRequest(async () => {
|
|
415
|
+
if (!Array.isArray(dataArray) || dataArray.length === 0) return 0;
|
|
416
|
+
await this.ensureTable(table, dataArray[0]);
|
|
417
|
+
|
|
418
|
+
const existingColumns = await this.query(`
|
|
419
|
+
SELECT column_name
|
|
420
|
+
FROM information_schema.columns
|
|
421
|
+
WHERE table_name = $1 AND table_schema = 'public'
|
|
422
|
+
`, [table]);
|
|
423
|
+
|
|
424
|
+
const existingColumnNames = existingColumns.map(col => col.column_name);
|
|
425
|
+
const keys = Object.keys(dataArray[0]);
|
|
426
|
+
|
|
427
|
+
// Eksik kolonları kontrol et ve ekle
|
|
428
|
+
for (const key of keys) {
|
|
429
|
+
if (!existingColumnNames.includes(key)) {
|
|
430
|
+
// Tüm değerleri kontrol ederek en uygun türü belirle
|
|
431
|
+
const columnValues = dataArray.map(obj => obj[key]).filter(val => val !== undefined && val !== null);
|
|
432
|
+
const columnType = columnValues.length > 0 ? this._getBestColumnType(columnValues) : 'TEXT';
|
|
433
|
+
await this.query(`ALTER TABLE "${table}" ADD COLUMN "${key}" ${columnType}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// PostgreSQL için VALUES clause oluştur
|
|
438
|
+
const placeholders = dataArray.map((_, rowIndex) =>
|
|
439
|
+
`(${keys.map((_, colIndex) => `$${rowIndex * keys.length + colIndex + 1}`).join(',')})`
|
|
440
|
+
).join(',');
|
|
441
|
+
|
|
442
|
+
const values = dataArray.flatMap(obj => keys.map(k => this._serializeValue(obj[k])));
|
|
443
|
+
const sql = `INSERT INTO "${table}" (${keys.map(k => `"${k}"`).join(",")}) VALUES ${placeholders}`;
|
|
444
|
+
|
|
445
|
+
const result = await this.query(sql, values);
|
|
446
|
+
return result.length;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async close() {
|
|
451
|
+
if (this.pool) await this.pool.end();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Helper to serialize values for storage
|
|
455
|
+
_serializeValue(value) {
|
|
456
|
+
if (value instanceof Date) {
|
|
457
|
+
return value.toISOString(); // PostgreSQL TIMESTAMP format
|
|
458
|
+
}
|
|
459
|
+
if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
|
|
460
|
+
return JSON.stringify(value); // PostgreSQL will handle JSONB conversion
|
|
461
|
+
}
|
|
462
|
+
return value;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Helper to deserialize values after retrieval
|
|
466
|
+
_deserializeValue(value) {
|
|
467
|
+
// PostgreSQL JSONB columns are automatically parsed by pg driver
|
|
468
|
+
// No need for manual JSON parsing unlike MySQL
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
module.exports = PostgreSQLDatabase;
|
package/database/sqlite.js
CHANGED
|
@@ -33,6 +33,93 @@ class SQLiteDatabase extends IDatabase{
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Değerin türünü otomatik olarak tespit eder ve SQLite türünü döndürür.
|
|
38
|
+
* @param {any} value - Kontrol edilecek değer
|
|
39
|
+
* @returns {string} SQLite veri türü
|
|
40
|
+
*/
|
|
41
|
+
_detectColumnType(value) {
|
|
42
|
+
if (value === null || value === undefined) {
|
|
43
|
+
return 'TEXT'; // Varsayılan olarak TEXT
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Boolean kontrolü
|
|
47
|
+
if (typeof value === 'boolean') {
|
|
48
|
+
return 'BOOLEAN';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Number kontrolü
|
|
52
|
+
if (typeof value === 'number') {
|
|
53
|
+
// Tam sayı mı ondalık mı kontrol et
|
|
54
|
+
if (Number.isInteger(value)) {
|
|
55
|
+
return 'INTEGER';
|
|
56
|
+
} else {
|
|
57
|
+
return 'REAL';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Date kontrolü
|
|
62
|
+
if (value instanceof Date) {
|
|
63
|
+
return 'DATETIME';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// String kontrolü - sayısal string'leri kontrol et
|
|
67
|
+
if (typeof value === 'string') {
|
|
68
|
+
// Boş string kontrolü
|
|
69
|
+
if (value.trim() === '') {
|
|
70
|
+
return 'TEXT';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Tam sayı string kontrolü
|
|
74
|
+
if (/^-?\d+$/.test(value.trim())) {
|
|
75
|
+
return 'INTEGER';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Ondalık sayı string kontrolü
|
|
79
|
+
if (/^-?\d+\.\d+$/.test(value.trim())) {
|
|
80
|
+
return 'REAL';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Boolean string kontrolü
|
|
84
|
+
const lowerValue = value.toLowerCase().trim();
|
|
85
|
+
if (lowerValue === 'true' || lowerValue === 'false') {
|
|
86
|
+
return 'BOOLEAN';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ISO date string kontrolü
|
|
90
|
+
if (value.match(/^\d{4}-\d{2}-\d{2}/) || value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
|
|
91
|
+
return 'DATETIME';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Varsayılan olarak TEXT
|
|
96
|
+
return 'TEXT';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Birden fazla değerden en uygun sütun türünü belirler.
|
|
101
|
+
* @param {Array} values - Değer dizisi
|
|
102
|
+
* @returns {string} En uygun SQLite veri türü
|
|
103
|
+
*/
|
|
104
|
+
_determineBestColumnType(values) {
|
|
105
|
+
const types = values.map(v => this._detectColumnType(v));
|
|
106
|
+
|
|
107
|
+
// Tür öncelik sırası: INTEGER < REAL < BOOLEAN < DATETIME < TEXT
|
|
108
|
+
const typePriority = {
|
|
109
|
+
'INTEGER': 1,
|
|
110
|
+
'REAL': 2,
|
|
111
|
+
'BOOLEAN': 3,
|
|
112
|
+
'DATETIME': 4,
|
|
113
|
+
'TEXT': 5
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// En yüksek öncelikli türü seç
|
|
117
|
+
const maxPriority = Math.max(...types.map(t => typePriority[t] || 5));
|
|
118
|
+
const bestType = Object.keys(typePriority).find(t => typePriority[t] === maxPriority);
|
|
119
|
+
|
|
120
|
+
return bestType || 'TEXT';
|
|
121
|
+
}
|
|
122
|
+
|
|
36
123
|
/**
|
|
37
124
|
* SQL sorgusu çalıştırır. Promise döndürür.
|
|
38
125
|
* @param {string} sql - Çalıştırılacak SQL sorgusu.
|
|
@@ -62,16 +149,26 @@ class SQLiteDatabase extends IDatabase{
|
|
|
62
149
|
* @param {object} data - Tablo oluşturulurken sütunları belirlemek için örnek veri.
|
|
63
150
|
*/
|
|
64
151
|
async ensureTable(table, data = {}) {
|
|
65
|
-
|
|
66
|
-
|
|
67
152
|
try {
|
|
68
153
|
await this.query(`SELECT 1 FROM \`${table}\` LIMIT 1`);
|
|
69
154
|
} catch (error) {
|
|
70
155
|
if (error.message.includes('no such table')) {
|
|
71
156
|
const columnDefinitions = Object.keys(data).map(col => {
|
|
72
|
-
const columnType =
|
|
157
|
+
const columnType = this._detectColumnType(data[col]);
|
|
73
158
|
return `\`${col}\` ${columnType}`;
|
|
74
|
-
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
let columnsPart = '';
|
|
162
|
+
if (columnDefinitions.length > 0) {
|
|
163
|
+
columnsPart = ', ' + columnDefinitions.join(", ");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const createTableSQL = `
|
|
167
|
+
CREATE TABLE \`${table}\` (
|
|
168
|
+
_id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
169
|
+
${columnsPart}
|
|
170
|
+
)
|
|
171
|
+
`;
|
|
75
172
|
await this.query(createTableSQL);
|
|
76
173
|
} else {
|
|
77
174
|
throw error;
|
|
@@ -89,7 +186,8 @@ class SQLiteDatabase extends IDatabase{
|
|
|
89
186
|
|
|
90
187
|
for (const key of Object.keys(data)) {
|
|
91
188
|
if (!existingNames.includes(key)) {
|
|
92
|
-
|
|
189
|
+
const columnType = this._detectColumnType(data[key]);
|
|
190
|
+
await this.query(`ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`);
|
|
93
191
|
}
|
|
94
192
|
}
|
|
95
193
|
}
|
|
@@ -214,8 +312,14 @@ class SQLiteDatabase extends IDatabase{
|
|
|
214
312
|
*/
|
|
215
313
|
async bulkInsert(table, dataArray) {
|
|
216
314
|
if (!Array.isArray(dataArray) || dataArray.length === 0) return 0;
|
|
315
|
+
|
|
316
|
+
// İlk elemanı tablo oluşturmak için kullan
|
|
217
317
|
await this.ensureTable(table, dataArray[0]);
|
|
218
|
-
|
|
318
|
+
|
|
319
|
+
// Tüm elemanların sütunlarını kontrol et
|
|
320
|
+
for (const item of dataArray) {
|
|
321
|
+
await this._ensureColumns(table, item);
|
|
322
|
+
}
|
|
219
323
|
|
|
220
324
|
const keys = Object.keys(dataArray[0]);
|
|
221
325
|
const placeholders = keys.map(() => '?').join(',');
|
|
@@ -247,4 +351,4 @@ class SQLiteDatabase extends IDatabase{
|
|
|
247
351
|
}
|
|
248
352
|
}
|
|
249
353
|
|
|
250
|
-
module.exports = SQLiteDatabase;
|
|
354
|
+
module.exports = SQLiteDatabase;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -239,6 +239,17 @@ const mongoDb = createDatabase({
|
|
|
239
239
|
database: 'my_app_mongo'
|
|
240
240
|
}
|
|
241
241
|
});
|
|
242
|
+
|
|
243
|
+
// --- 5. Using PG ---
|
|
244
|
+
const mongoDb = createDatabase({
|
|
245
|
+
adapter: 'postgres',
|
|
246
|
+
config: {
|
|
247
|
+
host: 'localhost',
|
|
248
|
+
user: 'root',
|
|
249
|
+
password: 'your_password',
|
|
250
|
+
database: 'my_app_prod'
|
|
251
|
+
}
|
|
252
|
+
});
|
|
242
253
|
```
|
|
243
254
|
|
|
244
255
|
### Basic Operations (CRUD)
|