Repository: Cog-Creators/Red-DiscordBot

Issue:
Red is spamming postgres with an invalid query 
# Other bugs

<!-- 
Did you find a bug with something other than a command? Fill out the following:
-->

#### What were you trying to do?

Was looking at my database logs for another issue.

#### What were you expecting to happen?

To find something related to my other issue.

#### What actually happened?

I found this:
![image](https://user-images.githubusercontent.com/16157130/85005638-32534d00-b1ad-11ea-8220-0ac0844f3e08.png)


#### How can we reproduce this issue?

I honestly have no clue, I'm hoping you guys know what's happening here.



Discussion:
All I see is Red trying to get a value that doesnt exist, which makes it use the default set by cogs.
Why is this ending up in my database logs then?
Because a query is being made for the value?
I guess this is a case where I'm just surprised because I've never seen a database log to the docker logs for query errors so far.

Can this be fixed on red's side? Something like if value not found then create default? Because I'd like to use the logs, and having them spammed with the same query from something else is a little harder to sort through.
Postgres data is not cached within red.

So everytime a valueis queried, and it doesnt exist in the database the error is raised which signals that the default value in memory should be used.

There doesn't really seem to be anything broken here as we need to know when a value doesnt exist to use the default.
Hmm, maybe there's a way to tweak the verbosity on Postgres's side then.


Resolving patch:
diff --git a/redbot/core/drivers/postgres/postgres.py b/redbot/core/drivers/postgres/postgres.py
--- a/redbot/core/drivers/postgres/postgres.py
+++ b/redbot/core/drivers/postgres/postgres.py
@@ -137,14 +137,11 @@ def get_config_details():
         }
 
     async def get(self, identifier_data: IdentifierData):
-        try:
-            result = await self._execute(
-                "SELECT red_config.get($1)",
-                encode_identifier_data(identifier_data),
-                method=self._pool.fetchval,
-            )
-        except asyncpg.UndefinedTableError:
-            raise KeyError from None
+        result = await self._execute(
+            "SELECT red_config.get($1)",
+            encode_identifier_data(identifier_data),
+            method=self._pool.fetchval,
+        )
 
         if result is None:
             # The result is None both when postgres yields no results, or when it yields a NULL row
@@ -163,12 +160,7 @@ async def set(self, identifier_data: IdentifierData, value=None):
             raise errors.CannotSetSubfield
 
     async def clear(self, identifier_data: IdentifierData):
-        try:
-            await self._execute(
-                "SELECT red_config.clear($1)", encode_identifier_data(identifier_data)
-            )
-        except asyncpg.UndefinedTableError:
-            pass
+        await self._execute("SELECT red_config.clear($1)", encode_identifier_data(identifier_data))
 
     async def inc(
         self, identifier_data: IdentifierData, value: Union[int, float], default: Union[int, float]


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -91,3 +91,13 @@ jobs:
             PGPASSWORD: postgres
             PGPORT: 5432
           run: tox
+        - name: Verify no errors in PostgreSQL logs.
+          run: |
+            logs="$(docker logs "${{ job.services.postgresql.id }}" 2>&1)"
+            echo "---- PostgreSQL logs ----"
+            echo "$logs"
+            echo "---- PostgreSQL logs ----"
+            error_count="$(echo "$logs" | { grep -c 'ERROR: ' || true; })"
+            if [[ $error_count -gt 0 ]]; then
+              exit 1
+            fi
diff --git a/tests/core/test_config.py b/tests/core/test_config.py
--- a/tests/core/test_config.py
+++ b/tests/core/test_config.py
@@ -660,3 +660,70 @@ async def test_config_custom_partial_pkeys_set(config, pkeys, raw_args, result):
     group = config.custom("TEST", *pkeys)
     await group.set_raw(*raw_args, value=result)
     assert await group.get_raw(*raw_args) == result
+
+
+@pytest.mark.asyncio
+async def test_config_custom_get_raw_with_default_on_whole_scope(config):
+    config.init_custom("TEST", 3)
+    config.register_custom("TEST")
+
+    group = config.custom("TEST")
+    assert await group.get_raw(default=True) is True
+
+
+@pytest.mark.parametrize(
+    "pkeys,raw_args,to_set",
+    (
+        # no config data for (cog_name, cog_id) is present
+        ((), (), None),
+        ((1,), (), None),
+        ((1, 2), (), None),
+        ((1, 2, 3), (), None),
+        ((1, 2, 3), ("key1",), None),
+        ((1, 2, 3), ("key1", "key2"), None),
+        # config data for (cog_name, cog_id) is present but scope does not exist
+        ((), (), ()),
+        ((1,), (), ()),
+        ((1, 2), (), ()),
+        ((1, 2, 3), (), ()),
+        ((1, 2, 3), ("key1",), ()),
+        ((1, 2, 3), ("key1", "key2"), ()),
+        # the scope exists with no records
+        ((1,), (), ("1",)),
+        ((1, 2), (), ("1",)),
+        ((1, 2, 3), (), ("1",)),
+        ((1, 2, 3), ("key1",), ("1",)),
+        ((1, 2, 3), ("key1", "key2"), ("1",)),
+        # scope with partial primary key (1,) exists
+        ((1, 2), (), ("1", "2")),
+        ((1, 2, 3), (), ("1", "2")),
+        ((1, 2, 3), ("key1",), ("1", "2")),
+        ((1, 2, 3), ("key1", "key2"), ("1", "2")),
+        # scope with partial primary key (1, 2) exists
+        ((1, 2, 3), (), ("1", "2", "3")),
+        ((1, 2, 3), ("key1",), ("1", "2", "3")),
+        ((1, 2, 3), ("key1", "key2"), ("1", "2", "3")),
+        # scope with full primary key (1, 2, 3)
+        ((1, 2, 3), ("key1",), ("1", "2", "3", "key1")),
+        ((1, 2, 3), ("key1", "key2"), ("1", "2", "3", "key1")),
+        # scope with full primary key (1, 2, 3) and a group named "key1" exists
+        ((1, 2, 3), ("key1", "key2"), ("1", "2", "3", "key1", "key2")),
+    ),
+)
+@pytest.mark.asyncio
+async def test_config_custom_clear_identifiers_that_do_not_exist(config, pkeys, raw_args, to_set):
+    config.init_custom("TEST", 3)
+    config.register_custom("TEST")
+
+    group = config.custom("TEST", *pkeys)
+    if to_set is not None:
+        data = {}
+        partial = data
+        for key in to_set:
+            partial[key] = {}
+            partial = partial[key]
+        scope = config.custom("TEST")
+        await scope.set(data)
+        # Clear needed to be able to differ between missing config data and missing scope data
+        await scope.clear_raw(*to_set)
+    await group.clear_raw(*raw_args)