UNPKG

@onurege3467/zerohelper

Version:

ZeroHelper is a versatile JavaScript library offering helper functions, validation, logging, database utilities and migration system for developers. It supports MongoDB, MySQL, SQLite, Redis, and PostgreSQL with increment/decrement operations.

551 lines (473 loc) 17.5 kB
const IDatabase = require('./IDatabase'); // Arayüzü import et /** * @implements {IDatabase} */ const sqlite3 = require('sqlite3').verbose(); const fs = require('fs'); const path = require('path'); class SQLiteDatabase extends IDatabase{ /** * @param {object} config - Yapılandırma nesnesi. * @param {string} config.filePath - SQLite veritabanı dosyasının yolu. */ constructor(config) { super() if (!config || !config.filePath) { throw new Error('SQLite yapılandırması için "filePath" gereklidir.'); } // Veritabanı dosyasının bulunacağı klasörün var olduğundan emin ol const dir = path.dirname(config.filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } this.db = new sqlite3.Database(config.filePath, (err) => { if (err) { console.error("SQLite connection error:", err.message); } else { console.log('Connected to the SQLite database.'); } }); } /** * Değerin türünü otomatik olarak tespit eder ve SQLite türünü döndürür. * @param {any} value - Kontrol edilecek değer * @returns {string} SQLite veri türü */ _detectColumnType(value) { if (value === null || value === undefined) { return 'TEXT'; // Varsayılan olarak TEXT } // Boolean kontrolü if (typeof value === 'boolean') { return 'BOOLEAN'; } // Number kontrolü if (typeof value === 'number') { // Tam sayı mı ondalık mı kontrol et if (Number.isInteger(value)) { return 'INTEGER'; } else { return 'REAL'; } } // Date kontrolü if (value instanceof Date) { return 'DATETIME'; } // String kontrolü - sayısal string'leri kontrol et if (typeof value === 'string') { // Boş string kontrolü if (value.trim() === '') { return 'TEXT'; } // Tam sayı string kontrolü if (/^-?\d+$/.test(value.trim())) { return 'INTEGER'; } // Ondalık sayı string kontrolü if (/^-?\d+\.\d+$/.test(value.trim())) { return 'REAL'; } // Boolean string kontrolü const lowerValue = value.toLowerCase().trim(); if (lowerValue === 'true' || lowerValue === 'false') { return 'BOOLEAN'; } // ISO date string kontrolü if (value.match(/^\d{4}-\d{2}-\d{2}/) || value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) { return 'DATETIME'; } } // Object ve Array kontrolü if (typeof value === 'object') { return 'TEXT'; // SQLite'da JSON olarak string halinde saklanacak } // Varsayılan olarak TEXT return 'TEXT'; } /** * Birden fazla değerden en uygun sütun türünü belirler. * @param {Array} values - Değer dizisi * @returns {string} En uygun SQLite veri türü */ _determineBestColumnType(values) { const types = values.map(v => this._detectColumnType(v)); // Tür öncelik sırası: INTEGER < REAL < BOOLEAN < DATETIME < TEXT const typePriority = { 'INTEGER': 1, 'REAL': 2, 'BOOLEAN': 3, 'DATETIME': 4, 'TEXT': 5 }; // En yüksek öncelikli türü seç const maxPriority = Math.max(...types.map(t => typePriority[t] || 5)); const bestType = Object.keys(typePriority).find(t => typePriority[t] === maxPriority); return bestType || 'TEXT'; } /** * Eksik kolonları kontrol eder ve ekler * @param {string} table - Tablo adı * @param {object} data - Kontrol edilecek veri */ async _ensureMissingColumns(table, data) { const columnsInfo = await this.query(`PRAGMA table_info(\`${table}\`)`); const existingNames = columnsInfo.map(col => col.name); for (const key of Object.keys(data)) { if (!existingNames.includes(key)) { const columnType = this._detectColumnType(data[key]); await this.query(`ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`); console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`); } } } /** * SQL sorgusu çalıştırır. Promise döndürür. * @param {string} sql - Çalıştırılacak SQL sorgusu. * @param {Array} params - Sorgu parametreleri. */ query(sql, params = []) { return new Promise((resolve, reject) => { // SELECT sorguları için .all() kullanılır if (sql.trim().toUpperCase().startsWith('SELECT') || sql.trim().toUpperCase().startsWith('PRAGMA')) { this.db.all(sql, params, (err, rows) => { if (err) reject(err); else resolve(rows); }); } else { // INSERT, UPDATE, DELETE gibi sorgular için .run() kullanılır this.db.run(sql, params, function (err) { if (err) reject(err); else resolve({ changes: this.changes, lastInsertRowid: this.lastID }); }); } }); } /** * Bir tablonun var olup olmadığını kontrol eder, yoksa oluşturur. * @param {string} table - Tablo adı. * @param {object} data - Tablo oluşturulurken sütunları belirlemek için örnek veri. */ async ensureTable(table, data = {}) { try { await this.query(`SELECT 1 FROM \`${table}\` LIMIT 1`); } catch (error) { if (error.message.includes('no such table')) { const columnDefinitions = Object.keys(data).map(col => { const columnType = this._detectColumnType(data[col]); return `\`${col}\` ${columnType}`; }); let columnsPart = ''; if (columnDefinitions.length > 0) { columnsPart = ', ' + columnDefinitions.join(", "); } const createTableSQL = ` CREATE TABLE \`${table}\` ( _id INTEGER PRIMARY KEY AUTOINCREMENT ${columnsPart} ) `; await this.query(createTableSQL); } else { throw error; } } } /** * Verilen verideki anahtarların tabloda sütun olarak var olduğundan emin olur. * @private * @deprecated Bu fonksiyon yerine _ensureMissingColumns kullanılıyor */ async _ensureColumns(table, data) { return this._ensureMissingColumns(table, data); } /** * Değeri serileştir (JSON objeler için) */ _serializeValue(value) { if (value instanceof Date) { return value.toISOString(); } if (Array.isArray(value) || (typeof value === 'object' && value !== null)) { return JSON.stringify(value); } return value; } /** * Değeri deserileştir (JSON objeler için) */ _deserializeValue(value) { if (typeof value === 'string') { try { // JSON string olup olmadığını kontrol et if (value.startsWith('[') || value.startsWith('{')) { const parsed = JSON.parse(value); if (typeof parsed === 'object' && parsed !== null) { return parsed; } } } catch (e) { // JSON parse hatası, orijinal değeri döndür } } return value; } /** * Bir tabloya yeni veri ekler. * @returns {Promise<number>} Eklenen satırın ID'si. */ async insert(table, data) { const copy = { ...data }; await this.ensureTable(table, copy); // Eksik kolonları ekle await this._ensureMissingColumns(table, copy); const keys = Object.keys(copy); const placeholders = keys.map(() => '?').join(','); const values = Object.values(copy).map(v => this._serializeValue(v)); const sql = `INSERT INTO \`${table}\` (${keys.map(k => `\`${k}\``).join(',')}) VALUES (${placeholders})`; const result = await this.query(sql, values); return result.lastInsertRowid; } /** * Tablodaki verileri günceller. * @returns {Promise<number>} Etkilenen satır sayısı. */ async update(table, data, where) { await this.ensureTable(table, { ...data, ...where }); // Eksik kolonları ekle await this._ensureMissingColumns(table, { ...data, ...where }); const setString = Object.keys(data).map(k => `\`${k}\` = ?`).join(', '); const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(' AND '); const sql = `UPDATE \`${table}\` SET ${setString} WHERE ${whereString}`; const result = await this.query(sql, [...Object.values(data).map(v => this._serializeValue(v)), ...Object.values(where).map(v => this._serializeValue(v))]); return result.changes; } /** * Tablodan veri siler. * @returns {Promise<number>} Etkilenen satır sayısı. */ async delete(table, where) { if (!where || Object.keys(where).length === 0) return 0; await this.ensureTable(table, { ...where }); // Eksik kolonları ekle (where koşulları için) await this._ensureMissingColumns(table, where); const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(' AND '); const sql = `DELETE FROM \`${table}\` WHERE ${whereString}`; const result = await this.query(sql, Object.values(where).map(v => this._serializeValue(v))); return result.changes; } /** * Tablodan veri seçer. * @returns {Promise<Array<object>>} Sonuç satırları. */ async select(table, where = null) { await this.ensureTable(table, where || {}); // Eğer where koşulu varsa, eksik kolonları ekle if (where && Object.keys(where).length > 0) { await this._ensureMissingColumns(table, where); } let sql = `SELECT * FROM \`${table}\``; let params = []; if (where && Object.keys(where).length > 0) { const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(' AND '); sql += ` WHERE ${whereString}`; params = Object.values(where).map(v => this._serializeValue(v)); } const rows = await this.query(sql, params); return rows.map(row => { const newRow = {}; for (const key in row) { newRow[key] = this._deserializeValue(row[key]); } return newRow; }); } /** * Veri varsa günceller, yoksa ekler (upsert). */ async set(table, data, where) { await this.ensureTable(table, { ...data, ...where }); // Eksik kolonları ekle await this._ensureMissingColumns(table, { ...data, ...where }); const existing = await this.select(table, where); if (existing.length === 0) { return await this.insert(table, { ...where, ...data }); } else { return await this.update(table, data, where); } } /** * Koşula uyan ilk veriyi seçer. * @returns {Promise<object|null>} Bulunan satır veya null. */ async selectOne(table, where = null) { const results = await this.select(table, where); return results[0] || null; } /** * Koşula uyan ilk veriyi siler. * @returns {Promise<number>} Etkilenen satır sayısı (0 veya 1). */ async deleteOne(table, where) { if (!where || Object.keys(where).length === 0) return 0; await this.ensureTable(table, where); // Eksik kolonları ekle (where koşulları için) await this._ensureMissingColumns(table, where); const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(' AND '); const sql = `DELETE FROM \`${table}\` WHERE rowid IN (SELECT rowid FROM \`${table}\` WHERE ${whereString} LIMIT 1)`; const result = await this.query(sql, Object.values(where).map(v => this._serializeValue(v))); return result.changes; } /** * Koşula uyan ilk veriyi günceller. * @returns {Promise<number>} Etkilenen satır sayısı (0 veya 1). */ async updateOne(table, data, where) { await this.ensureTable(table, { ...data, ...where }); // Eksik kolonları ekle await this._ensureMissingColumns(table, { ...data, ...where }); const setString = Object.keys(data).map(k => `\`${k}\` = ?`).join(', '); const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(' AND '); const sql = `UPDATE \`${table}\` SET ${setString} WHERE rowid IN (SELECT rowid FROM \`${table}\` WHERE ${whereString} LIMIT 1)`; const result = await this.query(sql, [...Object.values(data).map(v => this._serializeValue(v)), ...Object.values(where).map(v => this._serializeValue(v))]); return result.changes; } /** * Toplu veri ekleme. * @returns {Promise<number>} Eklenen satır sayısı. */ async bulkInsert(table, dataArray) { if (!Array.isArray(dataArray) || dataArray.length === 0) return 0; // İlk elemanı tablo oluşturmak için kullan await this.ensureTable(table, dataArray[0]); // Tüm datalardan gelen tüm anahtarları topla const allKeys = new Set(); dataArray.forEach(obj => { Object.keys(obj).forEach(key => allKeys.add(key)); }); // Tüm eksik kolonları ekle for (const key of allKeys) { const columnsInfo = await this.query(`PRAGMA table_info(\`${table}\`)`); const existingNames = columnsInfo.map(col => col.name); if (!existingNames.includes(key)) { // Tüm değerleri kontrol ederek en uygun türü belirle const columnValues = dataArray .map(obj => obj[key]) .filter(val => val !== undefined && val !== null); const columnType = columnValues.length > 0 ? this._determineBestColumnType(columnValues) : 'TEXT'; await this.query(`ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`); console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`); } } const keys = Array.from(allKeys); const placeholders = keys.map(() => '?').join(','); const sql = `INSERT INTO \`${table}\` (${keys.map(k => `\`${k}\``).join(',')}) VALUES (${placeholders})`; // sqlite3'te toplu ekleme için transaction kullan return new Promise((resolve, reject) => { const stmt = this.db.prepare(sql, (err) => { if (err) { reject(err); return; } }); this.db.serialize(() => { this.db.run("BEGIN TRANSACTION;"); let completed = 0; let hasError = false; for (const item of dataArray) { if (hasError) break; const values = keys.map(k => this._serializeValue(item[k])); stmt.run(values, (err) => { if (err && !hasError) { hasError = true; this.db.run("ROLLBACK;"); reject(err); return; } completed++; if (completed === dataArray.length && !hasError) { this.db.run("COMMIT;", (err) => { if (err) { reject(err); } else { stmt.finalize(); resolve(dataArray.length); } }); } }); } }); }); } /** * WHERE clause oluşturur * @param {object} where - WHERE koşulları * @returns {object} - whereClause string ve values array */ buildWhereClause(where = {}) { const conditions = Object.keys(where); if (conditions.length === 0) { return { whereClause: '', values: [] }; } const whereClause = ' WHERE ' + conditions.map(key => `${key} = ?`).join(' AND '); const values = Object.values(where); return { whereClause, values }; } /** * Numerik alanları artırır (increment). * @param {string} table - Verinin güncelleneceği tablo adı. * @param {object} increments - Artırılacak alanlar ve miktarları. * @param {object} where - Güncelleme koşulları. * @returns {Promise<number>} Etkilenen kayıt sayısı. */ async increment(table, increments, where = {}) { const incrementClauses = Object.keys(increments).map(field => `${field} = ${field} + ?` ).join(', '); const incrementValues = Object.values(increments); const { whereClause, values: whereValues } = this.buildWhereClause(where); const sql = `UPDATE ${table} SET ${incrementClauses}${whereClause}`; const allValues = [...incrementValues, ...whereValues]; return new Promise((resolve, reject) => { this.db.run(sql, allValues, function(err) { if (err) reject(err); else resolve(this.changes); }); }); } /** * Numerik alanları azaltır (decrement). * @param {string} table - Verinin güncelleneceği tablo adı. * @param {object} decrements - Azaltılacak alanlar ve miktarları. * @param {object} where - Güncelleme koşulları. * @returns {Promise<number>} Etkilenen kayıt sayısı. */ async decrement(table, decrements, where = {}) { const decrementClauses = Object.keys(decrements).map(field => `${field} = ${field} - ?` ).join(', '); const decrementValues = Object.values(decrements); const { whereClause, values: whereValues } = this.buildWhereClause(where); const sql = `UPDATE ${table} SET ${decrementClauses}${whereClause}`; const allValues = [...decrementValues, ...whereValues]; return new Promise((resolve, reject) => { this.db.run(sql, allValues, function(err) { if (err) reject(err); else resolve(this.changes); }); }); } /** * Veritabanı bağlantısını kapatır. */ async close() { return new Promise((resolve, reject) => { this.db.close((err) => { if (err) reject(err); else resolve(); }); }); } } module.exports = SQLiteDatabase;