@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
JavaScript
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;