// retoor import "sqlite" for Database import "uuid" for Uuid import "datetime" for DateTime import "json" for Json class Dataset { construct open(path) { _db = Database.open(path) _tables = {} } construct memory() { _db = Database.memory() _tables = {} } db { _db } [tableName] { if (!_tables.containsKey(tableName)) { _tables[tableName] = Table.new_(this, tableName) } return _tables[tableName] } tables { var rows = _db.query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") var result = [] for (row in rows) { result.add(row["name"]) } return result } close() { _db.close() } } class Table { construct new_(dataset, name) { _dataset = dataset _name = name _columns = null } name { _name } db { _dataset.db } columns { if (_columns == null) { _columns = {} var rows = db.query("PRAGMA table_info(" + _name + ")") for (row in rows) { _columns[row["name"]] = row["type"] } } return _columns } ensureTable_() { var exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?", [_name]) if (exists.count == 0) { db.execute("CREATE TABLE " + _name + " (uid TEXT PRIMARY KEY, created_at TEXT, deleted_at TEXT)") _columns = null } } ensureColumn_(colName, value) { ensureTable_() var cols = columns if (!cols.containsKey(colName)) { var sqlType = getSqlType_(value) db.execute("ALTER TABLE " + _name + " ADD COLUMN " + colName + " " + sqlType) _columns = null } } getSqlType_(value) { if (value is Num) { if (value == value.floor) return "INTEGER" return "REAL" } if (value is Bool) return "INTEGER" if (value is Map || value is List) return "TEXT" return "TEXT" } serializeValue_(value) { if (value is Bool) return value ? 1 : 0 if (value is Map || value is List) return Json.stringify(value) return value } deserializeRow_(row) { var result = {} for (key in row.keys) { var value = row[key] if (value is String && (value.startsWith("{") || value.startsWith("["))) { var fiber = Fiber.new { Json.parse(value) } var parsed = fiber.try() if (!fiber.error) { result[key] = parsed continue } } result[key] = value } return result } insert(record) { ensureTable_() var uid = record.containsKey("uid") ? record["uid"] : Uuid.v4() var createdAt = record.containsKey("created_at") ? record["created_at"] : DateTime.now().toString var colNames = ["uid", "created_at"] var placeholders = ["?", "?"] var values = [uid, createdAt] for (key in record.keys) { if (key == "uid" || key == "created_at" || key == "deleted_at") continue ensureColumn_(key, record[key]) colNames.add(key) placeholders.add("?") values.add(serializeValue_(record[key])) } var sql = "INSERT INTO " + _name + " (" + colNames.join(", ") + ") VALUES (" + placeholders.join(", ") + ")" db.execute(sql, values) var result = {} for (key in record.keys) { result[key] = record[key] } result["uid"] = uid result["created_at"] = createdAt return result } update(record) { if (!record.containsKey("uid")) { Fiber.abort("Record must have a uid for update") } var uid = record["uid"] var setParts = [] var values = [] for (key in record.keys) { if (key == "uid") continue ensureColumn_(key, record[key]) setParts.add(key + " = ?") values.add(serializeValue_(record[key])) } values.add(uid) var sql = "UPDATE " + _name + " SET " + setParts.join(", ") + " WHERE uid = ? AND deleted_at IS NULL" db.execute(sql, values) return db.changes } delete(uid) { var sql = "UPDATE " + _name + " SET deleted_at = ? WHERE uid = ? AND deleted_at IS NULL" db.execute(sql, [DateTime.now().toString, uid]) return db.changes > 0 } hardDelete(uid) { var sql = "DELETE FROM " + _name + " WHERE uid = ?" db.execute(sql, [uid]) return db.changes > 0 } find(conditions) { ensureTable_() var where = buildWhere_(conditions) var sql = "SELECT * FROM " + _name + " WHERE deleted_at IS NULL" + where["clause"] var rows = db.query(sql, where["values"]) var result = [] for (row in rows) { result.add(deserializeRow_(row)) } return result } findOne(conditions) { var results = find(conditions) return results.count > 0 ? results[0] : null } all() { ensureTable_() var rows = db.query("SELECT * FROM " + _name + " WHERE deleted_at IS NULL") var result = [] for (row in rows) { result.add(deserializeRow_(row)) } return result } count() { ensureTable_() var rows = db.query("SELECT COUNT(*) as cnt FROM " + _name + " WHERE deleted_at IS NULL") return rows[0]["cnt"] } buildWhere_(conditions) { var parts = [] var values = [] for (key in conditions.keys) { var value = conditions[key] var op = "=" var col = key if (key.contains("__")) { var split = key.split("__") col = split[0] var suffix = split[1] if (suffix == "gt") { op = ">" } else if (suffix == "lt") { op = "<" } else if (suffix == "gte") { op = ">=" } else if (suffix == "lte") { op = "<=" } else if (suffix == "ne") { op = "!=" } else if (suffix == "like") { op = "LIKE" } else if (suffix == "in") { if (value is List) { var placeholders = [] for (v in value) { placeholders.add("?") values.add(v) } parts.add(col + " IN (" + placeholders.join(", ") + ")") continue } } else if (suffix == "null") { if (value) { parts.add(col + " IS NULL") } else { parts.add(col + " IS NOT NULL") } continue } } parts.add(col + " " + op + " ?") values.add(serializeValue_(value)) } var clause = "" if (parts.count > 0) { clause = " AND " + parts.join(" AND ") } return {"clause": clause, "values": values} } }