diff --git a/isso/db/__init__.py b/isso/db/__init__.py index 7a10a74..cac31ee 100644 --- a/isso/db/__init__.py +++ b/isso/db/__init__.py @@ -2,8 +2,11 @@ import sqlite3 import logging +import operator import os.path +from collections import defaultdict + logger = logging.getLogger("isso") from isso.db.comments import Comments @@ -19,7 +22,7 @@ class SQLite3: a trigger for automated orphan removal. """ - MAX_VERSION = 2 + MAX_VERSION = 3 def __init__(self, path, conf): @@ -89,3 +92,25 @@ class SQLite3: con.execute('PRAGMA user_version = 2') logger.info("%i rows changed", con.total_changes) + + # limit max. nesting level to 1 + if self.version == 2: + + first = lambda rv: list(map(operator.itemgetter(0), rv)) + + with sqlite3.connect(self.path) as con: + top = first(con.execute("SELECT id FROM comments WHERE parent IS NULL").fetchall()) + flattened = defaultdict(set) + + for id in top: + + ids = [id, ] + + while ids: + rv = first(con.execute("SELECT id FROM comments WHERE parent=?", (ids.pop(), ))) + ids.extend(rv) + flattened[id].update(set(rv)) + + for id in flattened.keys(): + for n in flattened[id]: + con.execute("UPDATE comments SET parent=? WHERE id=?", (id, n)) diff --git a/isso/tests/test_db.py b/isso/tests/test_db.py index be354e4..0b5db33 100644 --- a/isso/tests/test_db.py +++ b/isso/tests/test_db.py @@ -9,8 +9,11 @@ import sqlite3 import tempfile from isso.db import SQLite3 +from isso.db.comments import Comments from isso.core import Config +from isso.compat import iteritems + class TestDBMigration(unittest.TestCase): @@ -49,3 +52,56 @@ class TestDBMigration(unittest.TestCase): self.assertEqual(db.version, SQLite3.MAX_VERSION) self.assertEqual(db.preferences.get("session-key"), "supersecretkey") + + def test_limit_nested_comments(self): + + tree = { + 1: None, + 2: None, + 3: 2, + 4: 3, + 7: 3, + 5: 2, + 6: None + } + + with sqlite3.connect(self.path) as con: + con.execute("PRAGMA user_version = 2") + con.execute("CREATE TABLE threads (" + " id INTEGER PRIMARY KEY," + " uri VARCHAR UNIQUE," + " title VARCHAR)") + con.execute("CREATE TABLE comments (" + " tid REFERENCES threads(id)," + " id INTEGER PRIMARY KEY," + " parent INTEGER," + " created FLOAT NOT NULL, modified FLOAT," + " text VARCHAR, email VARCHAR, website VARCHAR," + " mode INTEGER," + " remote_addr VARCHAR," + " likes INTEGER DEFAULT 0," + " dislikes INTEGER DEFAULT 0," + " voters BLOB)") + + con.execute("INSERT INTO threads (uri, title) VALUES (?, ?)", ("/", "Test")) + for (id, parent) in iteritems(tree): + con.execute("INSERT INTO comments (" + " tid, parent, created)" + "VALUEs (?, ?, ?)", (id, parent, id)) + + conf = Config.load(None) + db = SQLite3(self.path, conf) + + flattened = [ + (1, None), + (2, None), + (3, 2), + (4, 2), + (5, 2), + (6, None), + (7, 2) + ] + + with sqlite3.connect(self.path) as con: + rv = con.execute("SELECT id, parent FROM comments ORDER BY created").fetchall() + self.assertEqual(flattened, rv)