We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
bot.db.unalias_nick() behavior does not match docs
> ```
> Raises ValueError if there is not at least one other nick in the group.
> ```

The ValueError is never raised:

```
<dgw> .nickunmerge ThisIsATestNick
<Sopel> Removed ThisIsATestNick from nick group 1497.
<dgw> that's not supposed to work
<ThisIsATestNick> blerg
<dgw> .nickunmerge ThisIsATestNick
<Sopel> Removed ThisIsATestNick from nick group 1498.
```

I have traced this issue to a logic error, and will submit a proposed fix when I'm back in front of my laptop later tonight.

If it's more desirable to change the documentation than the behavior, I'd be happy to rewrite that instead—but I believe that the described behavior is correct, and the observed behavior is a bug.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sopel/db.py`
Content:
```
1 # coding=utf-8
2 from __future__ import unicode_literals, absolute_import, print_function, division
3 
4 import json
5 import os.path
6 import sys
7 import sqlite3
8 
9 from sopel.tools import Identifier
10 
11 if sys.version_info.major >= 3:
12     unicode = str
13     basestring = str
14 
15 
16 def _deserialize(value):
17     if value is None:
18         return None
19     # sqlite likes to return ints for strings that look like ints, even though
20     # the column type is string. That's how you do dynamic typing wrong.
21     value = unicode(value)
22     # Just in case someone's mucking with the DB in a way we can't account for,
23     # ignore json parsing errors
24     try:
25         value = json.loads(value)
26     except:
27         pass
28     return value
29 
30 
31 class SopelDB(object):
32     """*Availability: 5.0+*
33 
34     This defines an interface for basic, common operations on a sqlite
35     database. It simplifies those common operations, and allows direct access
36     to the database, wherever the user has configured it to be.
37 
38     When configured with a relative filename, it is assumed to be in the same
39     directory as the config."""
40 
41     def __init__(self, config):
42         path = config.core.db_filename
43         config_dir, config_file = os.path.split(config.filename)
44         config_name, _ = os.path.splitext(config_file)
45         if path is None:
46             path = os.path.join(config_dir, config_name + '.db')
47         path = os.path.expanduser(path)
48         if not os.path.isabs(path):
49             path = os.path.normpath(os.path.join(config_dir, path))
50         self.filename = path
51         self._create()
52 
53     def connect(self):
54         """Return a raw database connection object."""
55         return sqlite3.connect(self.filename)
56 
57     def execute(self, *args, **kwargs):
58         """Execute an arbitrary SQL query against the database.
59 
60         Returns a cursor object, on which things like `.fetchall()` can be
61         called per PEP 249."""
62         with self.connect() as conn:
63             cur = conn.cursor()
64             return cur.execute(*args, **kwargs)
65 
66     def _create(self):
67         """Create the basic database structure."""
68         # Do nothing if the db already exists.
69         try:
70             self.execute('SELECT * FROM nick_ids;')
71             self.execute('SELECT * FROM nicknames;')
72             self.execute('SELECT * FROM nick_values;')
73             self.execute('SELECT * FROM channel_values;')
74         except:
75             pass
76         else:
77             return
78 
79         self.execute(
80             'CREATE TABLE nick_ids (nick_id INTEGER PRIMARY KEY AUTOINCREMENT)'
81         )
82         self.execute(
83             'CREATE TABLE nicknames '
84             '(nick_id INTEGER REFERENCES nick_ids, '
85             'slug STRING PRIMARY KEY, canonical string)'
86         )
87         self.execute(
88             'CREATE TABLE nick_values '
89             '(nick_id INTEGER REFERENCES nick_ids(nick_id), '
90             'key STRING, value STRING, '
91             'PRIMARY KEY (nick_id, key))'
92         )
93         self.execute(
94             'CREATE TABLE channel_values '
95             '(channel STRING, key STRING, value STRING, '
96             'PRIMARY KEY (channel, key))'
97         )
98 
99     def get_uri(self):
100         """Returns a URL for the database, usable to connect with SQLAlchemy.
101         """
102         return 'sqlite://{}'.format(self.filename)
103 
104     # NICK FUNCTIONS
105 
106     def get_nick_id(self, nick, create=True):
107         """Return the internal identifier for a given nick.
108 
109         This identifier is unique to a user, and shared across all of that
110         user's aliases. If create is True, a new ID will be created if one does
111         not already exist"""
112         slug = nick.lower()
113         nick_id = self.execute('SELECT nick_id from nicknames where slug = ?',
114                                [slug]).fetchone()
115         if nick_id is None:
116             if not create:
117                 raise ValueError('No ID exists for the given nick')
118             with self.connect() as conn:
119                 cur = conn.cursor()
120                 cur.execute('INSERT INTO nick_ids VALUES (NULL)')
121                 nick_id = cur.execute('SELECT last_insert_rowid()').fetchone()[0]
122                 cur.execute(
123                     'INSERT INTO nicknames (nick_id, slug, canonical) VALUES '
124                     '(?, ?, ?)',
125                     [nick_id, slug, nick]
126                 )
127             nick_id = self.execute('SELECT nick_id from nicknames where slug = ?',
128                                    [slug]).fetchone()
129         return nick_id[0]
130 
131     def alias_nick(self, nick, alias):
132         """Create an alias for a nick.
133 
134         Raises ValueError if the alias already exists. If nick does not already
135         exist, it will be added along with the alias."""
136         nick = Identifier(nick)
137         alias = Identifier(alias)
138         nick_id = self.get_nick_id(nick)
139         sql = 'INSERT INTO nicknames (nick_id, slug, canonical) VALUES (?, ?, ?)'
140         values = [nick_id, alias.lower(), alias]
141         try:
142             self.execute(sql, values)
143         except sqlite3.IntegrityError:
144             raise ValueError('Alias already exists.')
145 
146     def set_nick_value(self, nick, key, value):
147         """Sets the value for a given key to be associated with the nick."""
148         nick = Identifier(nick)
149         value = json.dumps(value, ensure_ascii=False)
150         nick_id = self.get_nick_id(nick)
151         self.execute('INSERT OR REPLACE INTO nick_values VALUES (?, ?, ?)',
152                      [nick_id, key, value])
153 
154     def get_nick_value(self, nick, key):
155         """Retrieves the value for a given key associated with a nick."""
156         nick = Identifier(nick)
157         result = self.execute(
158             'SELECT value FROM nicknames JOIN nick_values '
159             'ON nicknames.nick_id = nick_values.nick_id '
160             'WHERE slug = ? AND key = ?',
161             [nick.lower(), key]
162         ).fetchone()
163         if result is not None:
164             result = result[0]
165         return _deserialize(result)
166 
167     def unalias_nick(self, alias):
168         """Removes an alias.
169 
170         Raises ValueError if there is not at least one other nick in the group.
171         To delete an entire group, use `delete_group`.
172         """
173         alias = Identifier(alias)
174         nick_id = self.get_nick_id(alias, False)
175         count = self.execute('SELECT COUNT(*) FROM nicknames WHERE nick_id = ?',
176                              [nick_id]).fetchone()[0]
177         if count == 0:
178             raise ValueError('Given alias is the only entry in its group.')
179         self.execute('DELETE FROM nicknames WHERE slug = ?', [alias.lower()])
180 
181     def delete_nick_group(self, nick):
182         """Removes a nickname, and all associated aliases and settings.
183         """
184         nick = Identifier(nick)
185         nick_id = self.get_nick_id(nick, False)
186         self.execute('DELETE FROM nicknames WHERE nick_id = ?', [nick_id])
187         self.execute('DELETE FROM nick_values WHERE nick_id = ?', [nick_id])
188 
189     def merge_nick_groups(self, first_nick, second_nick):
190         """Merges the nick groups for the specified nicks.
191 
192         Takes two nicks, which may or may not be registered.  Unregistered
193         nicks will be registered. Keys which are set for only one of the given
194         nicks will be preserved. Where multiple nicks have values for a given
195         key, the value set for the first nick will be used.
196 
197         Note that merging of data only applies to the native key-value store.
198         If modules define their own tables which rely on the nick table, they
199         will need to have their merging done separately."""
200         first_id = self.get_nick_id(Identifier(first_nick))
201         second_id = self.get_nick_id(Identifier(second_nick))
202         self.execute(
203             'UPDATE OR IGNORE nick_values SET nick_id = ? WHERE nick_id = ?',
204             [first_id, second_id])
205         self.execute('DELETE FROM nick_values WHERE nick_id = ?', [second_id])
206         self.execute('UPDATE nicknames SET nick_id = ? WHERE nick_id = ?',
207                      [first_id, second_id])
208 
209     # CHANNEL FUNCTIONS
210 
211     def set_channel_value(self, channel, key, value):
212         channel = Identifier(channel).lower()
213         value = json.dumps(value, ensure_ascii=False)
214         self.execute('INSERT OR REPLACE INTO channel_values VALUES (?, ?, ?)',
215                      [channel, key, value])
216 
217     def get_channel_value(self, channel, key):
218         """Retrieves the value for a given key associated with a channel."""
219         channel = Identifier(channel).lower()
220         result = self.execute(
221             'SELECT value FROM channel_values WHERE channel = ? AND key = ?',
222             [channel, key]
223         ).fetchone()
224         if result is not None:
225             result = result[0]
226         return _deserialize(result)
227 
228     # NICK AND CHANNEL FUNCTIONS
229 
230     def get_nick_or_channel_value(self, name, key):
231         """Gets the value `key` associated to the nick or channel  `name`.
232         """
233         name = Identifier(name)
234         if name.is_nick():
235             return self.get_nick_value(name, key)
236         else:
237             return self.get_channel_value(name, key)
238 
239     def get_preferred_value(self, names, key):
240         """Gets the value for the first name which has it set.
241 
242         `names` is a list of channel and/or user names. Returns None if none of
243         the names have the key set."""
244         for name in names:
245             value = self.get_nick_or_channel_value(name, key)
246             if value is not None:
247                 return value
248
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sopel/db.py b/sopel/db.py
--- a/sopel/db.py
+++ b/sopel/db.py
@@ -174,7 +174,7 @@
         nick_id = self.get_nick_id(alias, False)
         count = self.execute('SELECT COUNT(*) FROM nicknames WHERE nick_id = ?',
                              [nick_id]).fetchone()[0]
-        if count == 0:
+        if count <= 1:
             raise ValueError('Given alias is the only entry in its group.')
         self.execute('DELETE FROM nicknames WHERE slug = ?', [alias.lower()])

{"golden_diff": "diff --git a/sopel/db.py b/sopel/db.py\n--- a/sopel/db.py\n+++ b/sopel/db.py\n@@ -174,7 +174,7 @@\n         nick_id = self.get_nick_id(alias, False)\n         count = self.execute('SELECT COUNT(*) FROM nicknames WHERE nick_id = ?',\n                              [nick_id]).fetchone()[0]\n-        if count == 0:\n+        if count <= 1:\n             raise ValueError('Given alias is the only entry in its group.')\n         self.execute('DELETE FROM nicknames WHERE slug = ?', [alias.lower()])\n", "issue": "bot.db.unalias_nick() behavior does not match docs\n> ```\n> Raises ValueError if there is not at least one other nick in the group.\n> ```\n\nThe ValueError is never raised:\n\n```\n<dgw> .nickunmerge ThisIsATestNick\n<Sopel> Removed ThisIsATestNick from nick group 1497.\n<dgw> that's not supposed to work\n<ThisIsATestNick> blerg\n<dgw> .nickunmerge ThisIsATestNick\n<Sopel> Removed ThisIsATestNick from nick group 1498.\n```\n\nI have traced this issue to a logic error, and will submit a proposed fix when I'm back in front of my laptop later tonight.\n\nIf it's more desirable to change the documentation than the behavior, I'd be happy to rewrite that instead\u2014but I believe that the described behavior is correct, and the observed behavior is a bug.\n\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nimport json\nimport os.path\nimport sys\nimport sqlite3\n\nfrom sopel.tools import Identifier\n\nif sys.version_info.major >= 3:\n    unicode = str\n    basestring = str\n\n\ndef _deserialize(value):\n    if value is None:\n        return None\n    # sqlite likes to return ints for strings that look like ints, even though\n    # the column type is string. That's how you do dynamic typing wrong.\n    value = unicode(value)\n    # Just in case someone's mucking with the DB in a way we can't account for,\n    # ignore json parsing errors\n    try:\n        value = json.loads(value)\n    except:\n        pass\n    return value\n\n\nclass SopelDB(object):\n    \"\"\"*Availability: 5.0+*\n\n    This defines an interface for basic, common operations on a sqlite\n    database. It simplifies those common operations, and allows direct access\n    to the database, wherever the user has configured it to be.\n\n    When configured with a relative filename, it is assumed to be in the same\n    directory as the config.\"\"\"\n\n    def __init__(self, config):\n        path = config.core.db_filename\n        config_dir, config_file = os.path.split(config.filename)\n        config_name, _ = os.path.splitext(config_file)\n        if path is None:\n            path = os.path.join(config_dir, config_name + '.db')\n        path = os.path.expanduser(path)\n        if not os.path.isabs(path):\n            path = os.path.normpath(os.path.join(config_dir, path))\n        self.filename = path\n        self._create()\n\n    def connect(self):\n        \"\"\"Return a raw database connection object.\"\"\"\n        return sqlite3.connect(self.filename)\n\n    def execute(self, *args, **kwargs):\n        \"\"\"Execute an arbitrary SQL query against the database.\n\n        Returns a cursor object, on which things like `.fetchall()` can be\n        called per PEP 249.\"\"\"\n        with self.connect() as conn:\n            cur = conn.cursor()\n            return cur.execute(*args, **kwargs)\n\n    def _create(self):\n        \"\"\"Create the basic database structure.\"\"\"\n        # Do nothing if the db already exists.\n        try:\n            self.execute('SELECT * FROM nick_ids;')\n            self.execute('SELECT * FROM nicknames;')\n            self.execute('SELECT * FROM nick_values;')\n            self.execute('SELECT * FROM channel_values;')\n        except:\n            pass\n        else:\n            return\n\n        self.execute(\n            'CREATE TABLE nick_ids (nick_id INTEGER PRIMARY KEY AUTOINCREMENT)'\n        )\n        self.execute(\n            'CREATE TABLE nicknames '\n            '(nick_id INTEGER REFERENCES nick_ids, '\n            'slug STRING PRIMARY KEY, canonical string)'\n        )\n        self.execute(\n            'CREATE TABLE nick_values '\n            '(nick_id INTEGER REFERENCES nick_ids(nick_id), '\n            'key STRING, value STRING, '\n            'PRIMARY KEY (nick_id, key))'\n        )\n        self.execute(\n            'CREATE TABLE channel_values '\n            '(channel STRING, key STRING, value STRING, '\n            'PRIMARY KEY (channel, key))'\n        )\n\n    def get_uri(self):\n        \"\"\"Returns a URL for the database, usable to connect with SQLAlchemy.\n        \"\"\"\n        return 'sqlite://{}'.format(self.filename)\n\n    # NICK FUNCTIONS\n\n    def get_nick_id(self, nick, create=True):\n        \"\"\"Return the internal identifier for a given nick.\n\n        This identifier is unique to a user, and shared across all of that\n        user's aliases. If create is True, a new ID will be created if one does\n        not already exist\"\"\"\n        slug = nick.lower()\n        nick_id = self.execute('SELECT nick_id from nicknames where slug = ?',\n                               [slug]).fetchone()\n        if nick_id is None:\n            if not create:\n                raise ValueError('No ID exists for the given nick')\n            with self.connect() as conn:\n                cur = conn.cursor()\n                cur.execute('INSERT INTO nick_ids VALUES (NULL)')\n                nick_id = cur.execute('SELECT last_insert_rowid()').fetchone()[0]\n                cur.execute(\n                    'INSERT INTO nicknames (nick_id, slug, canonical) VALUES '\n                    '(?, ?, ?)',\n                    [nick_id, slug, nick]\n                )\n            nick_id = self.execute('SELECT nick_id from nicknames where slug = ?',\n                                   [slug]).fetchone()\n        return nick_id[0]\n\n    def alias_nick(self, nick, alias):\n        \"\"\"Create an alias for a nick.\n\n        Raises ValueError if the alias already exists. If nick does not already\n        exist, it will be added along with the alias.\"\"\"\n        nick = Identifier(nick)\n        alias = Identifier(alias)\n        nick_id = self.get_nick_id(nick)\n        sql = 'INSERT INTO nicknames (nick_id, slug, canonical) VALUES (?, ?, ?)'\n        values = [nick_id, alias.lower(), alias]\n        try:\n            self.execute(sql, values)\n        except sqlite3.IntegrityError:\n            raise ValueError('Alias already exists.')\n\n    def set_nick_value(self, nick, key, value):\n        \"\"\"Sets the value for a given key to be associated with the nick.\"\"\"\n        nick = Identifier(nick)\n        value = json.dumps(value, ensure_ascii=False)\n        nick_id = self.get_nick_id(nick)\n        self.execute('INSERT OR REPLACE INTO nick_values VALUES (?, ?, ?)',\n                     [nick_id, key, value])\n\n    def get_nick_value(self, nick, key):\n        \"\"\"Retrieves the value for a given key associated with a nick.\"\"\"\n        nick = Identifier(nick)\n        result = self.execute(\n            'SELECT value FROM nicknames JOIN nick_values '\n            'ON nicknames.nick_id = nick_values.nick_id '\n            'WHERE slug = ? AND key = ?',\n            [nick.lower(), key]\n        ).fetchone()\n        if result is not None:\n            result = result[0]\n        return _deserialize(result)\n\n    def unalias_nick(self, alias):\n        \"\"\"Removes an alias.\n\n        Raises ValueError if there is not at least one other nick in the group.\n        To delete an entire group, use `delete_group`.\n        \"\"\"\n        alias = Identifier(alias)\n        nick_id = self.get_nick_id(alias, False)\n        count = self.execute('SELECT COUNT(*) FROM nicknames WHERE nick_id = ?',\n                             [nick_id]).fetchone()[0]\n        if count == 0:\n            raise ValueError('Given alias is the only entry in its group.')\n        self.execute('DELETE FROM nicknames WHERE slug = ?', [alias.lower()])\n\n    def delete_nick_group(self, nick):\n        \"\"\"Removes a nickname, and all associated aliases and settings.\n        \"\"\"\n        nick = Identifier(nick)\n        nick_id = self.get_nick_id(nick, False)\n        self.execute('DELETE FROM nicknames WHERE nick_id = ?', [nick_id])\n        self.execute('DELETE FROM nick_values WHERE nick_id = ?', [nick_id])\n\n    def merge_nick_groups(self, first_nick, second_nick):\n        \"\"\"Merges the nick groups for the specified nicks.\n\n        Takes two nicks, which may or may not be registered.  Unregistered\n        nicks will be registered. Keys which are set for only one of the given\n        nicks will be preserved. Where multiple nicks have values for a given\n        key, the value set for the first nick will be used.\n\n        Note that merging of data only applies to the native key-value store.\n        If modules define their own tables which rely on the nick table, they\n        will need to have their merging done separately.\"\"\"\n        first_id = self.get_nick_id(Identifier(first_nick))\n        second_id = self.get_nick_id(Identifier(second_nick))\n        self.execute(\n            'UPDATE OR IGNORE nick_values SET nick_id = ? WHERE nick_id = ?',\n            [first_id, second_id])\n        self.execute('DELETE FROM nick_values WHERE nick_id = ?', [second_id])\n        self.execute('UPDATE nicknames SET nick_id = ? WHERE nick_id = ?',\n                     [first_id, second_id])\n\n    # CHANNEL FUNCTIONS\n\n    def set_channel_value(self, channel, key, value):\n        channel = Identifier(channel).lower()\n        value = json.dumps(value, ensure_ascii=False)\n        self.execute('INSERT OR REPLACE INTO channel_values VALUES (?, ?, ?)',\n                     [channel, key, value])\n\n    def get_channel_value(self, channel, key):\n        \"\"\"Retrieves the value for a given key associated with a channel.\"\"\"\n        channel = Identifier(channel).lower()\n        result = self.execute(\n            'SELECT value FROM channel_values WHERE channel = ? AND key = ?',\n            [channel, key]\n        ).fetchone()\n        if result is not None:\n            result = result[0]\n        return _deserialize(result)\n\n    # NICK AND CHANNEL FUNCTIONS\n\n    def get_nick_or_channel_value(self, name, key):\n        \"\"\"Gets the value `key` associated to the nick or channel  `name`.\n        \"\"\"\n        name = Identifier(name)\n        if name.is_nick():\n            return self.get_nick_value(name, key)\n        else:\n            return self.get_channel_value(name, key)\n\n    def get_preferred_value(self, names, key):\n        \"\"\"Gets the value for the first name which has it set.\n\n        `names` is a list of channel and/or user names. Returns None if none of\n        the names have the key set.\"\"\"\n        for name in names:\n            value = self.get_nick_or_channel_value(name, key)\n            if value is not None:\n                return value\n", "path": "sopel/db.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nimport json\nimport os.path\nimport sys\nimport sqlite3\n\nfrom sopel.tools import Identifier\n\nif sys.version_info.major >= 3:\n    unicode = str\n    basestring = str\n\n\ndef _deserialize(value):\n    if value is None:\n        return None\n    # sqlite likes to return ints for strings that look like ints, even though\n    # the column type is string. That's how you do dynamic typing wrong.\n    value = unicode(value)\n    # Just in case someone's mucking with the DB in a way we can't account for,\n    # ignore json parsing errors\n    try:\n        value = json.loads(value)\n    except:\n        pass\n    return value\n\n\nclass SopelDB(object):\n    \"\"\"*Availability: 5.0+*\n\n    This defines an interface for basic, common operations on a sqlite\n    database. It simplifies those common operations, and allows direct access\n    to the database, wherever the user has configured it to be.\n\n    When configured with a relative filename, it is assumed to be in the same\n    directory as the config.\"\"\"\n\n    def __init__(self, config):\n        path = config.core.db_filename\n        config_dir, config_file = os.path.split(config.filename)\n        config_name, _ = os.path.splitext(config_file)\n        if path is None:\n            path = os.path.join(config_dir, config_name + '.db')\n        path = os.path.expanduser(path)\n        if not os.path.isabs(path):\n            path = os.path.normpath(os.path.join(config_dir, path))\n        self.filename = path\n        self._create()\n\n    def connect(self):\n        \"\"\"Return a raw database connection object.\"\"\"\n        return sqlite3.connect(self.filename)\n\n    def execute(self, *args, **kwargs):\n        \"\"\"Execute an arbitrary SQL query against the database.\n\n        Returns a cursor object, on which things like `.fetchall()` can be\n        called per PEP 249.\"\"\"\n        with self.connect() as conn:\n            cur = conn.cursor()\n            return cur.execute(*args, **kwargs)\n\n    def _create(self):\n        \"\"\"Create the basic database structure.\"\"\"\n        # Do nothing if the db already exists.\n        try:\n            self.execute('SELECT * FROM nick_ids;')\n            self.execute('SELECT * FROM nicknames;')\n            self.execute('SELECT * FROM nick_values;')\n            self.execute('SELECT * FROM channel_values;')\n        except:\n            pass\n        else:\n            return\n\n        self.execute(\n            'CREATE TABLE nick_ids (nick_id INTEGER PRIMARY KEY AUTOINCREMENT)'\n        )\n        self.execute(\n            'CREATE TABLE nicknames '\n            '(nick_id INTEGER REFERENCES nick_ids, '\n            'slug STRING PRIMARY KEY, canonical string)'\n        )\n        self.execute(\n            'CREATE TABLE nick_values '\n            '(nick_id INTEGER REFERENCES nick_ids(nick_id), '\n            'key STRING, value STRING, '\n            'PRIMARY KEY (nick_id, key))'\n        )\n        self.execute(\n            'CREATE TABLE channel_values '\n            '(channel STRING, key STRING, value STRING, '\n            'PRIMARY KEY (channel, key))'\n        )\n\n    def get_uri(self):\n        \"\"\"Returns a URL for the database, usable to connect with SQLAlchemy.\n        \"\"\"\n        return 'sqlite://{}'.format(self.filename)\n\n    # NICK FUNCTIONS\n\n    def get_nick_id(self, nick, create=True):\n        \"\"\"Return the internal identifier for a given nick.\n\n        This identifier is unique to a user, and shared across all of that\n        user's aliases. If create is True, a new ID will be created if one does\n        not already exist\"\"\"\n        slug = nick.lower()\n        nick_id = self.execute('SELECT nick_id from nicknames where slug = ?',\n                               [slug]).fetchone()\n        if nick_id is None:\n            if not create:\n                raise ValueError('No ID exists for the given nick')\n            with self.connect() as conn:\n                cur = conn.cursor()\n                cur.execute('INSERT INTO nick_ids VALUES (NULL)')\n                nick_id = cur.execute('SELECT last_insert_rowid()').fetchone()[0]\n                cur.execute(\n                    'INSERT INTO nicknames (nick_id, slug, canonical) VALUES '\n                    '(?, ?, ?)',\n                    [nick_id, slug, nick]\n                )\n            nick_id = self.execute('SELECT nick_id from nicknames where slug = ?',\n                                   [slug]).fetchone()\n        return nick_id[0]\n\n    def alias_nick(self, nick, alias):\n        \"\"\"Create an alias for a nick.\n\n        Raises ValueError if the alias already exists. If nick does not already\n        exist, it will be added along with the alias.\"\"\"\n        nick = Identifier(nick)\n        alias = Identifier(alias)\n        nick_id = self.get_nick_id(nick)\n        sql = 'INSERT INTO nicknames (nick_id, slug, canonical) VALUES (?, ?, ?)'\n        values = [nick_id, alias.lower(), alias]\n        try:\n            self.execute(sql, values)\n        except sqlite3.IntegrityError:\n            raise ValueError('Alias already exists.')\n\n    def set_nick_value(self, nick, key, value):\n        \"\"\"Sets the value for a given key to be associated with the nick.\"\"\"\n        nick = Identifier(nick)\n        value = json.dumps(value, ensure_ascii=False)\n        nick_id = self.get_nick_id(nick)\n        self.execute('INSERT OR REPLACE INTO nick_values VALUES (?, ?, ?)',\n                     [nick_id, key, value])\n\n    def get_nick_value(self, nick, key):\n        \"\"\"Retrieves the value for a given key associated with a nick.\"\"\"\n        nick = Identifier(nick)\n        result = self.execute(\n            'SELECT value FROM nicknames JOIN nick_values '\n            'ON nicknames.nick_id = nick_values.nick_id '\n            'WHERE slug = ? AND key = ?',\n            [nick.lower(), key]\n        ).fetchone()\n        if result is not None:\n            result = result[0]\n        return _deserialize(result)\n\n    def unalias_nick(self, alias):\n        \"\"\"Removes an alias.\n\n        Raises ValueError if there is not at least one other nick in the group.\n        To delete an entire group, use `delete_group`.\n        \"\"\"\n        alias = Identifier(alias)\n        nick_id = self.get_nick_id(alias, False)\n        count = self.execute('SELECT COUNT(*) FROM nicknames WHERE nick_id = ?',\n                             [nick_id]).fetchone()[0]\n        if count <= 1:\n            raise ValueError('Given alias is the only entry in its group.')\n        self.execute('DELETE FROM nicknames WHERE slug = ?', [alias.lower()])\n\n    def delete_nick_group(self, nick):\n        \"\"\"Removes a nickname, and all associated aliases and settings.\n        \"\"\"\n        nick = Identifier(nick)\n        nick_id = self.get_nick_id(nick, False)\n        self.execute('DELETE FROM nicknames WHERE nick_id = ?', [nick_id])\n        self.execute('DELETE FROM nick_values WHERE nick_id = ?', [nick_id])\n\n    def merge_nick_groups(self, first_nick, second_nick):\n        \"\"\"Merges the nick groups for the specified nicks.\n\n        Takes two nicks, which may or may not be registered.  Unregistered\n        nicks will be registered. Keys which are set for only one of the given\n        nicks will be preserved. Where multiple nicks have values for a given\n        key, the value set for the first nick will be used.\n\n        Note that merging of data only applies to the native key-value store.\n        If modules define their own tables which rely on the nick table, they\n        will need to have their merging done separately.\"\"\"\n        first_id = self.get_nick_id(Identifier(first_nick))\n        second_id = self.get_nick_id(Identifier(second_nick))\n        self.execute(\n            'UPDATE OR IGNORE nick_values SET nick_id = ? WHERE nick_id = ?',\n            [first_id, second_id])\n        self.execute('DELETE FROM nick_values WHERE nick_id = ?', [second_id])\n        self.execute('UPDATE nicknames SET nick_id = ? WHERE nick_id = ?',\n                     [first_id, second_id])\n\n    # CHANNEL FUNCTIONS\n\n    def set_channel_value(self, channel, key, value):\n        channel = Identifier(channel).lower()\n        value = json.dumps(value, ensure_ascii=False)\n        self.execute('INSERT OR REPLACE INTO channel_values VALUES (?, ?, ?)',\n                     [channel, key, value])\n\n    def get_channel_value(self, channel, key):\n        \"\"\"Retrieves the value for a given key associated with a channel.\"\"\"\n        channel = Identifier(channel).lower()\n        result = self.execute(\n            'SELECT value FROM channel_values WHERE channel = ? AND key = ?',\n            [channel, key]\n        ).fetchone()\n        if result is not None:\n            result = result[0]\n        return _deserialize(result)\n\n    # NICK AND CHANNEL FUNCTIONS\n\n    def get_nick_or_channel_value(self, name, key):\n        \"\"\"Gets the value `key` associated to the nick or channel  `name`.\n        \"\"\"\n        name = Identifier(name)\n        if name.is_nick():\n            return self.get_nick_value(name, key)\n        else:\n            return self.get_channel_value(name, key)\n\n    def get_preferred_value(self, names, key):\n        \"\"\"Gets the value for the first name which has it set.\n\n        `names` is a list of channel and/or user names. Returns None if none of\n        the names have the key set.\"\"\"\n        for name in names:\n            value = self.get_nick_or_channel_value(name, key)\n            if value is not None:\n                return value\n", "path": "sopel/db.py"}]}