Beispiel #1
0
    def get_aggregations(self, first_day, last_day):
        """ Get aggregations between two given days (inclusive).
        If the last day is today, also include the current aggregation.
        """
        assert isinstance(first_day, datetime.date)
        assert isinstance(last_day, datetime.date)
        today = time.gmtime()  # UTC
        today = datetime.date(today.tm_year, today.tm_mon, today.tm_mday)
        one_day = datetime.timedelta(days=1)

        data = []

        db = ItemDB(self.filename)
        try:
            data = db.select(
                TABLE_NAME,
                "time_key >= ? AND time_key < ?",
                first_day.strftime("%Y-%m-%d"),
                (last_day + one_day).strftime("%Y-%m-%d"),
            )
        except KeyError:
            pass  # Invalid table name

        if last_day == today:
            data.append(self.get_current_aggr())

        return data
Beispiel #2
0
def test_database_race_conditions():

    # This actually tests that a specific update scheme works with the
    # itemdb. It should. In a previous version, itemdb was specifically
    # designed for this syncing task. Now it's more general, but this
    # is still a good use-case.

    n_threads = 25
    n_writes = 25
    tracking = {}
    for i in range(1, 11):
        tracking[i] = []

    # Create db and ensure it has tables
    filename = get_fresh_filename()
    ItemDB(filename).ensure_table("items", "!id")

    def push_a_bunch():
        for i in range(n_writes):
            id = random.randint(1, 10)
            mt = random.randint(1000, 2000)
            tracking[id].append(mt)
            with ItemDB(filename) as db:
                x = db.select_one("items", "id == ?", id)
                if not x or x["mt"] <= mt:
                    db.put_one("items", id=id, mt=mt)

    # Prepare, start, and join threads
    t0 = time.perf_counter()
    threads = [threading.Thread(target=push_a_bunch) for i in range(n_threads)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    t1 = time.perf_counter()

    # Evaluate the result
    items = ItemDB(filename).select_all("items")
    print(
        f"{t1 - t0:0.2f} s for {n_threads * n_writes} writes saving {len(items)} items"
    )
    assert len(items) == 10  # that's the number of id's
    #
    for item in items:
        id = item["id"]
        assert item["mt"] == max(tracking[id])

    return items
Beispiel #3
0
def test_multiple_unique_keys():

    db = ItemDB(":memory:").ensure_table("items", "!id1", "!id2")

    with db:
        db.put_one("items", id1=1, id2=1, value=1)
        db.put_one("items", id1=1, id2=2, value=2)
        db.put_one("items", id1=2, id2=2, value=3)
        db.put_one("items", id1=2, id2=1, value=4)

    assert db.count_all("items") == 1
    assert db.select_one("items", "id1 == 1") is None
    assert db.select_one("items", "id1 == 2")["value"] == 4
Beispiel #4
0
 def push_a_bunch():
     for i in range(n_writes):
         id = random.randint(1, 10)
         mt = random.randint(1000, 2000)
         tracking[id].append(mt)
         with ItemDB(filename) as db:
             x = db.select_one("items", "id == ?", id)
             if not x or x["mt"] <= mt:
                 db.put_one("items", id=id, mt=mt)
Beispiel #5
0
def test_table_fails():

    db = ItemDB(":memory:")
    for name in [(), 4, b"", [], {}]:
        with raises(TypeError):  # not a str
            db.ensure_table(name)

    db = ItemDB(":memory:")
    for name in ["foo bar", "foo-bar", "33", "foo!", "!foo"]:
        with raises(ValueError):  # not an identifier
            db.ensure_table(name)
Beispiel #6
0
def get_user_db(user):
    """Open the user db and return the db and its mtime (which is -1 if the db did not yet exist)."""
    # Get database name and its mtime
    dbname = user2filename(user)
    if os.path.isfile(dbname):
        mtime = os.path.getmtime(dbname)
    else:
        mtime = -1
    # Open the database, this creates it if it does not yet exist
    db = ItemDB(dbname)
    return db, mtime
Beispiel #7
0
def test_create_tables():

    # Empty database, zero tables

    db = ItemDB(":memory:")

    assert db.get_table_names() == []  # no tables

    # Two tables

    db = ItemDB(":memory:").ensure_table("foo", "key").ensure_table("bar")

    assert db.get_table_names() == ["bar", "foo"]
    assert db.count_all("foo") == 0
    assert db.count_all("bar") == 0
Beispiel #8
0
def test_transactions2():

    filename = get_fresh_filename()
    with ItemDB(filename).ensure_table("items", "!id") as db:
        db.put_one("items", id=3, value=10)

    # run transactions in threads while reading from other threads
    def run_slow_transaction1():
        db = ItemDB(filename)
        with db:
            db.put_one("items", id=3, value=20)
            time.sleep(1.0)

    def run_fast_transaction2():
        db = ItemDB(filename)
        time.sleep(0.1)  # make sure that we're the waiting thread
        with db:
            db.put_one("items", id=3, value=30)
            time.sleep(0.2)

    def run_read():
        db = ItemDB(filename)
        for i in range(30):
            time.sleep(0.05)
            item = db.select_one("items", "id == 3")
            read.append(item["value"])

    read = []
    threads = [
        threading.Thread(target=run_slow_transaction1),
        threading.Thread(target=run_fast_transaction2),
        threading.Thread(target=run_read),
    ]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    assert len(read) == 30
    assert 5 <= read.count(10) <= 22
    assert 2 <= read.count(20) <= 6
    assert read.count(30) >= 5
Beispiel #9
0
 def __init__(self, filename, *, step=DEFAULT_STEP):
     self._step = int(step)
     # Prepare db
     self._filename = filename
     # Locks
     self._tlocal = threading.local()  # per-thread data
     self._lock_current_aggr = threading.RLock()
     # Init current aggregation
     self._current_aggr = self._create_new_aggr()
     self._current_time_stop = self._current_aggr["time_stop"]
     # Keep track of ids for daily counters
     self._daily_ids = {}  # key -> set of ids, gets cleared each day
     self._monthly_ids = {}
     if os.path.isfile(self._filename):
         db = ItemDB(self._filename)
         try:
             db.ensure_table("info", "!key")
             daily_ids_info = db.select_one("info", "key == 'daily_ids'")
             day_key = self._current_aggr["time_key"][:10]
             if daily_ids_info and daily_ids_info[
                     "time_key"][:10] == day_key:
                 for key in daily_ids_info:
                     if key not in ("key", "time_key"):
                         self._daily_ids[key] = set(daily_ids_info[key])
             monthly_ids_info = db.select_one("info",
                                              "key == 'monthly_ids'")
             month_key = self._current_aggr["time_key"][:7]
             if monthly_ids_info and monthly_ids_info[
                     "time_key"][:7] == month_key:
                 for key in monthly_ids_info:
                     if key not in ("key", "time_key"):
                         self._monthly_ids[key] = set(monthly_ids_info[key])
         except Exception as err:
             logger.error(
                 f"Failed to restore daily_ids and monthly_ids from db: {err}"
             )
     # Setup our helper thread
     _monitor_instances.add(self)
     global _helper_thread
     if _helper_thread is None:
         _helper_thread = HelperThread()
         _helper_thread.start()
Beispiel #10
0
 def write_something():
     db = ItemDB(filename).ensure_table("settings", "!id")
     with db:
         db.put("settings", dict(id="server_reset", value="42", mt=42))
     db.close()
     return "wrote something"
Beispiel #11
0
 def read_something():
     db = ItemDB(filename).ensure_table("settings", "!id")
     xx.extend(db.select_all("settings"))
     return "read something"
Beispiel #12
0
def test_delete_table():

    db = ItemDB(":memory:")
    db.ensure_table("persons", "!name")
    db.ensure_table("animals", "!name")
    with db:
        db.put_one("persons", name="Jan", age=30)
        db.put_one("persons", name="Henk", age=42)
        db.put_one("animals", name="Takkie", age=30)
        db.put_one("animals", name="Siepe", age=42)

    assert db.count_all("persons") == 2
    assert db.count_all("animals") == 2

    with db:
        db.delete_table("persons")

    with raises(KeyError):
        db.count_all("persons")
    db.ensure_table("persons", "!name")
    assert db.count_all("persons") == 0
    assert db.count_all("animals") == 2

    with db:
        db.delete_table("animals")

    with raises(KeyError):
        db.count_all("animals")
    db.ensure_table("animals", "!name")
    assert db.count_all("persons") == 0
    assert db.count_all("animals") == 0

    # Need a transaction context
    with raises(IOError):
        db.delete_table("persons")
    # This works
    with db:
        db.delete_table("persons")
    # But this not because the table is gone
    with raises(KeyError):
        with db:
            db.delete_table("persons")
Beispiel #13
0
def test_init_read():

    # Empty database, zero tables

    db = ItemDB(":memory:")

    assert db.get_table_names() == []  # no tables

    with raises(KeyError):
        db.select("foo", "key is NULL")
    with raises(KeyError):
        db.select_all("foo")
    with raises(KeyError):
        db.count_all("foo")

    # Two tables

    db = ItemDB(":memory:").ensure_table("foo", "key").ensure_table("bar")
    assert db.count_all("foo") == 0
    assert db.count_all("bar") == 0
Beispiel #14
0
def test_usage_settings():

    db = ItemDB(":memory:").ensure_table("settings", "!id", "mt", "value")

    # Need id
    with raises(IndexError):
        with db:
            db.put("settings", dict(value="old", mt=100))

    # Add three items
    with db:
        db.put("settings", dict(id="foo", value="old", mt=100))
        db.put("settings", dict(id="bar", value="old", mt=100))
        db.put("settings", dict(id="egg", value="old", mt=100))

    assert len(db.select_all("settings")) == 3
    assert len(db.select("settings", "mt > 100")) == 0
    assert len(db.select("settings", "value == 'updated'")) == 0

    # Update them, one using an older
    for item in [
            dict(id="foo", value="updated", mt=99),
            dict(id="bar", value="updated", mt=100),  # also updates
            dict(id="egg", value="updated", mt=101),
            dict(id="spam", value="updated", mt=101),  # new
    ]:
        with db:
            cur = db.select("settings", "id == ?", item["id"])
            if not cur or cur[0]["mt"] <= item["mt"]:
                db.put("settings", item)

    assert len(db.select_all("settings")) == 4
    assert len(db.select("settings", "mt > 100")) == 2
    assert len(db.select("settings", "value == 'updated'")) == 3
    assert db.select_one("settings", "id=='egg'")["value"] == "updated"
Beispiel #15
0
 def run_slow_transaction():
     db = ItemDB(filename)
     with db:
         time.sleep(0.2)
Beispiel #16
0
 def run_slow_transaction1():
     db = ItemDB(filename)
     with db:
         db.put_one("items", id=3, value=20)
         time.sleep(1.0)
def get_from_db(what):
    filename = apiserver.user2filename(USER)
    return ItemDB(filename).select_all(what)
Beispiel #18
0
def test_index_fails():

    # Invalid index/table names - not str
    db = ItemDB(":memory:")
    for name in [(), 4, b"", [], {}]:
        with raises(TypeError):
            db.ensure_table("items", name)

    # Invalid index/table names - not an identifier
    db = ItemDB(":memory:")
    for name in ["foo bar", "foo-bar", "33", "foo!"]:
        with raises(ValueError):
            db.ensure_table("items", name)

    # Reserved
    for name in ["!_ob", "_ob"]:
        with raises(IndexError):
            db.ensure_table("items", name)

    # Cannot add a unique key

    filename = get_fresh_filename()
    db = ItemDB(filename).ensure_table("foo", "meh")
    with closing(db):

        assert "foo" in db.get_table_names()

        with raises(IndexError):
            db = ItemDB(filename).ensure_table("foo", "!key")

    # Cannot use a normal key as a unique key

    filename = get_fresh_filename()
    db = ItemDB(filename).ensure_table("foo", "key")
    with closing(db):

        assert "foo" in db.get_table_names()

        with raises(IndexError):
            db = ItemDB(filename).ensure_table("foo", "!key")

    # Cannot use a unique key as a normal key

    filename = get_fresh_filename()
    db = ItemDB(filename).ensure_table("foo", "!key")
    with closing(db):

        assert "foo" in db.get_table_names()

        with raises(IndexError):
            db = ItemDB(filename).ensure_table("foo", "key")
Beispiel #19
0
def test_change_unique_key():

    db = ItemDB(":memory:")
    db.ensure_table("persons", "!name")
    with db:
        db.put_one("persons", name="Jan", age=30)
        db.put_one("persons", name="Henk", age=42)

    assert db.count_all("persons") == 2

    # Add a new person, who also happens to be named "Jan"
    with db:
        db.put_one("persons", name="Jan", age=72)
    # Sorry, Jan
    assert db.count_all("persons") == 2

    # Let's fix this, we need a separate id, so we need to re-index.
    # We cannot simply do this on an existing table. So we need some steps.
    try:
        with db:
            db.ensure_table("persons2")
            for i, person in enumerate(db.select_all("persons")):
                person["id"] = i
                db.put("persons2", person)
            db.delete_table("persons")
            raise RuntimeError("Oop! Something goes wrong in the process")
            db.rename_table("persons2", "persons")
    except RuntimeError:
        pass

    # Our little operation failed, but we did it in a transaction, so its fine!
    assert db.count_all("persons") == 2

    # Try again
    with db:
        db.ensure_table("persons2")
        for i, person in enumerate(db.select_all("persons")):
            person["id"] = i
            db.put("persons2", person)
        db.delete_table("persons")
        db.rename_table("persons2", "persons")

    # Now we're good
    assert db.count_all("persons") == 2
    with db:
        db.put_one("persons", name="Jan", age=72, id=3)
    assert db.count_all("persons") == 3
Beispiel #20
0
def test_transactions1():

    filename = get_fresh_filename()

    db = ItemDB(filename)
    db.ensure_table("items", "!id", "mt")

    # Add items the easy way
    with db:
        db.put_one("items", id=1, mt=100)
        db.put_one("items", id=2, mt=100)
    assert db.count_all("items") == 2

    # Add more items and raise after
    with raises(RuntimeError):
        with db:
            db.put_one("items", id=3, mt=100)
            db.put_one("items", id=4, mt=100)
        raise RuntimeError("Transaction has been comitted")
    assert db.count_all("items") == 4

    # Again, but now raise within transaction
    with raises(RuntimeError):
        with db:
            db.put_one("items", id=5, mt=100)
            db.put_one("items", id=6, mt=100)
            raise RuntimeError("Abort transaction!")
    assert db.count_all("items") == 4
Beispiel #21
0
def test_delete_items():

    db = ItemDB(":memory:")
    db.ensure_table("persons", "!name")
    with db:
        db.put_one("persons", name="Jan", age=30)
        db.put_one("persons", name="Henk", age=42)
        db.put_one("persons", name="Bart", age=19)
        db.put_one("persons", name="Ivo", age=28)

    assert db.select_one("persons", "name == ?", "Bart") == {
        "name": "Bart",
        "age": 19
    }

    # Delete fails
    with raises(IOError):  # Must be in a transaction!
        db.delete("persons", "name == ?", "Bart")
    with raises(IndexError):  # No index for age
        with db:
            db.delete("persons", "age == 42")
    with raises(sqlite3.OperationalError):  # Malformed SQL
        with db:
            db.delete("persons", "age >>> 42")

    with db:
        db.delete("persons", "name == ?", "Bart")

    assert db.count_all("persons") == 3
    assert db.select_one("persons", "name == ?", "Bart") is None

    # And that transaction can be cancelled
    try:
        with db:
            db.delete("persons", "name > ''")
            raise RuntimeError()
    except RuntimeError:
        pass

    assert db.count_all("persons") == 3

    # Just to show that without that raise, it would indeed clear the table
    with db:
        db.delete("persons", "name > ''")
    assert db.count_all("persons") == 0
Beispiel #22
0
def test_multiple_items():

    filename = get_fresh_filename()

    db = ItemDB(filename)
    db.ensure_table("items", "!id")

    assert len(db.select_all("items")) == 0

    # Adding multiple
    with db:
        db.put("items", dict(id=1, mt=100), dict(id=2, mt=100))

    assert len(db.select_all("items")) == 2

    # Separate additions, one gets added
    # These few tests here are a remnant of when itemdb was different, but lets
    # not throw away precious testing code ...
    with db:
        db.put("items", dict(id=3, mt=100))
    with raises(RuntimeError):
        with db:
            raise RuntimeError()

    assert set(x["id"] for x in db.select_all("items")) == {1, 2, 3}

    # Combined addition, none gets added
    with raises(RuntimeError):
        with db:
            db.put("items", dict(id=4, mt=100), dict(id=5))
            raise RuntimeError()

    assert set(x["id"] for x in db.select_all("items")) == {1, 2, 3}

    # Combined addition, none gets changed
    with raises(RuntimeError):
        with db:
            db.put("items", dict(id=3, mt=102), dict(id=5))
            raise RuntimeError()

    assert set(x["id"] for x in db.select_all("items")) == {1, 2, 3}
    x = db.select_all("items")[-1]
    assert x["id"] == 3 and x["mt"] == 100

    # Upgrades work too
    db = ItemDB(filename)

    with db:
        db.put(
            "items",
            dict(id=1, mt=102),
            dict(id=1, mt=102),
            dict(id=2, mt=102),
            dict(id=3, mt=102),
            dict(id=4, mt=102),
        )
    assert set(x["id"] for x in db.select_all("items")) == {1, 2, 3, 4}
    for x in db.select_all("items"):
        x["mt"] == 102

    # Lets take it further
    with db:
        db.put("items", *(dict(id=i, mt=104) for i in range(99)))
    assert len(db.select_all("items")) == 99
Beispiel #23
0
def test_init_write():
    db = ItemDB(":memory:").ensure_table("items", "!id", "mt")

    with raises(IOError):  # Put needs to be used under a context
        db.put("items", dict(id=1, mt=100))

    with raises(KeyError):  # Invalid table
        with db:
            db.put("foo", dict(id=1, mt=100))

    with raises(TypeError):  # Note a dict
        with db:
            db.put("items", "not a dict")

    with raises(IndexError):  # id is required but missing
        with db:
            db.put("items", dict(mt=100))

    with raises(IOError):  # Cant enter twice
        with db:
            with db:
                pass

    with db:
        db.put("items", dict(id=1, mt=100))
        db.put("items", dict(id=2, mt=100, value=42))
        db.put("items", dict(id=3, value=42))

    assert len(db.select_all("items")) == 3
    assert db.count_all("items") == 3
    assert len(db.get_table_names()) == 1

    assert len(db.select("items", "mt == 100")) == 2
    assert len(db.select("items", "mt is NULL")) == 1
    assert db.count("items", "mt == 100") == 2
    assert db.count("items", "mt is NULL") == 1
    with raises(IndexError):  # No index for value
        db.select("items", "value == 42")
    with raises(IndexError):  # No index for value
        db.count("items", "value == 42")
    with raises(sqlite3.OperationalError):  # Malformed SQL
        db.select("items", "id >>> 42")
    with raises(sqlite3.OperationalError):  # Malformed SQL
        db.count("items", "id >>> 42")
Beispiel #24
0
 def _write_aggr(self, aggr):
     """ Write the given aggr to disk. Used by the helper thread to write
     aggr's that we put on the _write_queue.
     """
     for key in aggr.keys():
         if not key.startswith("time_"):
             break
     else:
         return  # Nothing in here, return now
     try:
         db = ItemDB(self._filename)
         # Write aggegation
         db.ensure_table(TABLE_NAME, "!time_key")
         with db:
             x = db.select_one(TABLE_NAME, "time_key == ?",
                               aggr["time_key"])
             if x is not None:
                 merge(x, aggr)
                 aggr = x
             db.put(TABLE_NAME, aggr)
         # Prepare daily ids info
         daily_ids_info = {}
         for key in self._daily_ids.keys():
             daily_ids_info[key] = list(self._daily_ids[key])
         daily_ids_info["key"] = "daily_ids"
         daily_ids_info["time_key"] = self._current_aggr["time_key"][:10]
         # Prepare montly ids info
         monthly_ids_info = {}
         for key in self._monthly_ids.keys():
             monthly_ids_info[key] = list(self._monthly_ids[key])
         monthly_ids_info["key"] = "monthly_ids"
         monthly_ids_info["time_key"] = self._current_aggr["time_key"][:7]
         # Write
         db.ensure_table("info", "!key")
         with db:
             db.put("info", daily_ids_info)
             db.put("info", monthly_ids_info)
     except Exception as err:
         logger.error("Failed to save aggregations: " + str(err))
Beispiel #25
0
import requests
import json
import math
from itemdb import ItemDB
from build import Build
from build import Item
import build_types
import timeit

itemdb = ItemDB()


# importing wynn item DB for stashing
def get_items():
    try:
        with open('item_file.json', "r") as item_file:
            item_list = json.load(item_file)
            print("successfully loaded")
    except:
        request = requests.get(
            'https://api.wynncraft.com/public_api.php?action=itemDB&category=all'
        )
        item_list = request.json()['items']

    with open('item_file.json', "w") as item_file:
        json.dump(item_list, item_file)
    return item_list


itemdb.add_json(get_items())
epic = Build()
Beispiel #26
0
def test_missing_values2():

    filename = get_fresh_filename()

    db = ItemDB(filename)
    db.ensure_table("items", "!id", "mt")

    # Keys that are not listed are NOT ignored
    with db:
        db.put("items", dict(id=1, mt=100))
        db.put("items", dict(id=2, mt=100, value=6))
    #
    assert db.select_all("items") == [
        dict(id=1, mt=100), dict(id=2, mt=100, value=6)
    ]
    with raises(IndexError):  # No index for value
        db.select("items", "value == 6")

    # When a column is added it gets NULL values in the db, and items stay as they are
    db.ensure_table("items", "value")
    with db:
        db.put("items", dict(id=3, mt=100, value=41))
    #
    assert db.select_all("items") == [
        dict(id=1, mt=100),
        dict(id=2, mt=100, value=6),
        dict(id=3, mt=100, value=41),
    ]

    assert len(db.select("items", "value == 6")) == 1
    assert len(db.select("items", "value > 0")) == 2
    assert len(db.select("items", "value is NULL")) == 1

    # When we don't specify a column, it still gets a value (not NULL)
    db = ItemDB(filename)
    with db:
        db.put("items", dict(id=5, mt=100, value=999))
    assert len(db.select("items", "value == 999")) == 1
Beispiel #27
0
 def run_fast_transaction2():
     db = ItemDB(filename)
     time.sleep(0.1)  # make sure that we're the waiting thread
     with db:
         db.put_one("items", id=3, value=30)
         time.sleep(0.2)
Beispiel #28
0
def test_usage_items():

    db = ItemDB(":memory:").ensure_table("items", "!id", "mt", "value")

    # Need id
    with raises(IndexError):
        with db:
            db.put("items", dict(mt=100, value=1))

    # Add three items
    with db:
        db.put("items", dict(id=1, mt=100, value=1))
        db.put("items", dict(id=2, mt=100, value=1))
        db.put("items", dict(id=3, mt=100, value=1))

    assert len(db.select_all("items")) == 3
    assert len(db.select("items", "value == 1")) == 3
    assert len(db.select("items", "value == 2")) == 0

    # Update them, one using an older mt
    for item in [
            dict(id=1, mt=99, value=2),  # wont override
            dict(id=2, mt=100, value=2),  # will override - mt's are equal
            dict(id=3, mt=101, value=2),  # will override
            dict(id=4, mt=101, value=2),  # new
    ]:
        with db:
            cur = db.select("items", "id == ?", item["id"])
            if not cur or cur[0]["mt"] <= item["mt"]:
                db.put("items", item)

    assert len(db.select_all("items")) == 4
    assert len(db.select("items", "value == 1")) == 1
    assert len(db.select("items", "value == 2")) == 3

    x = db.select_one("items", "id == ?", 3)
    assert x["mt"] == 101

    db = ItemDB(":memory:").ensure_table("items", "!id", "mt", "value")
    x = db.select_one("items", "id == ?", 3)
    assert x is None
Beispiel #29
0
 def run_read():
     db = ItemDB(filename)
     for i in range(30):
         time.sleep(0.05)
         item = db.select_one("items", "id == 3")
         read.append(item["value"])
Beispiel #30
0
def test_rename_table():

    db = ItemDB(":memory:")
    db.ensure_table("persons", "!name")
    with db:
        db.put_one("persons", name="Jan", age=30)
        db.put_one("persons", name="Henk", age=42)
        db.put_one("persons", name="Takkie", age=30)
        db.put_one("persons", name="Siepe", age=42)

    assert db.count_all("persons") == 4
    with raises(KeyError):
        db.count_all("clients")

    # Fails
    with raises(IOError):  # Need a transaction context
        db.rename_table("persons", "clients")
    with raises(TypeError):  # not a str
        with db:
            db.rename_table("persons", 3)
    with raises(TypeError):  # not an identifier
        with db:
            db.rename_table("persons", "foo bar")

    with db:
        db.rename_table("persons", "clients")

    assert db.count_all("clients") == 4
    with raises(KeyError):
        db.count_all("persons")