Commit 2915abb9 authored by Lukas Fleischer's avatar Lukas Fleischer
Browse files

git-interface: Add database abstraction layer



Add a new class that connects to the database specified in the
configuration file and provides an interface to execute SQL queries.
Prepared statements with qmark ("?") placeholders are supported.

Replace all direct database accesses with calls to the new abstraction
layer.
Signed-off-by: Lukas Fleischer's avatarLukas Fleischer <lfleischer@archlinux.org>
parent 2cd69bf6
import configparser
import mysql.connector
import os
class Connection:
_conn = None
def __init__(self):
config = configparser.RawConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config")
aur_db_host = config.get('database', 'host')
aur_db_name = config.get('database', 'name')
aur_db_user = config.get('database', 'user')
aur_db_pass = config.get('database', 'password')
aur_db_socket = config.get('database', 'socket')
self._conn = mysql.connector.connect(host=aur_db_host,
user=aur_db_user,
passwd=aur_db_pass,
db=aur_db_name,
unix_socket=aur_db_socket,
buffered=True)
def execute(self, query, params=()):
query = query.replace('%', '%%').replace('?', '%s')
cur = self._conn.cursor()
cur.execute(query, params)
return cur
def commit(self):
self._conn.commit()
def close(self):
self._conn.close()
#!/usr/bin/python3 #!/usr/bin/python3
import configparser import configparser
import mysql.connector
import shlex import shlex
import os import os
import re import re
import sys import sys
import db
def format_command(env_vars, command, ssh_opts, ssh_key): def format_command(env_vars, command, ssh_opts, ssh_key):
environment = '' environment = ''
...@@ -26,12 +27,6 @@ def format_command(env_vars, command, ssh_opts, ssh_key): ...@@ -26,12 +27,6 @@ def format_command(env_vars, command, ssh_opts, ssh_key):
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config")
aur_db_host = config.get('database', 'host')
aur_db_name = config.get('database', 'name')
aur_db_user = config.get('database', 'user')
aur_db_pass = config.get('database', 'password')
aur_db_socket = config.get('database', 'socket')
valid_keytypes = config.get('auth', 'valid-keytypes').split() valid_keytypes = config.get('auth', 'valid-keytypes').split()
username_regex = config.get('auth', 'username-regex') username_regex = config.get('auth', 'username-regex')
git_serve_cmd = config.get('auth', 'git-serve-cmd') git_serve_cmd = config.get('auth', 'git-serve-cmd')
...@@ -42,14 +37,11 @@ keytext = sys.argv[2] ...@@ -42,14 +37,11 @@ keytext = sys.argv[2]
if keytype not in valid_keytypes: if keytype not in valid_keytypes:
exit(1) exit(1)
db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, conn = db.Connection()
passwd=aur_db_pass, db=aur_db_name,
unix_socket=aur_db_socket, buffered=True)
cur = db.cursor() cur = conn.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " +
cur.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " +
"INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID " "INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID "
"WHERE SSHPubKeys.PubKey = %s AND Users.Suspended = 0", "WHERE SSHPubKeys.PubKey = ? AND Users.Suspended = 0",
(keytype + " " + keytext,)) (keytype + " " + keytext,))
if cur.rowcount != 1: if cur.rowcount != 1:
......
#!/usr/bin/python3 #!/usr/bin/python3
import configparser import configparser
import mysql.connector
import os import os
import re import re
import shlex import shlex
import sys import sys
import db
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config")
aur_db_host = config.get('database', 'host')
aur_db_name = config.get('database', 'name')
aur_db_user = config.get('database', 'user')
aur_db_pass = config.get('database', 'password')
aur_db_socket = config.get('database', 'socket')
repo_path = config.get('serve', 'repo-path') repo_path = config.get('serve', 'repo-path')
repo_regex = config.get('serve', 'repo-regex') repo_regex = config.get('serve', 'repo-regex')
git_shell_cmd = config.get('serve', 'git-shell-cmd') git_shell_cmd = config.get('serve', 'git-shell-cmd')
...@@ -27,12 +22,8 @@ maintenance_exc = config.get('options', 'maintenance-exceptions').split() ...@@ -27,12 +22,8 @@ maintenance_exc = config.get('options', 'maintenance-exceptions').split()
def pkgbase_from_name(pkgbase): def pkgbase_from_name(pkgbase):
db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, conn = db.Connection()
passwd=aur_db_pass, db=aur_db_name, cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase])
unix_socket=aur_db_socket)
cur = db.cursor()
cur.execute("SELECT ID FROM PackageBases WHERE Name = %s", [pkgbase])
db.close()
row = cur.fetchone() row = cur.fetchone()
return row[0] if row else None return row[0] if row else None
...@@ -43,21 +34,18 @@ def pkgbase_exists(pkgbase): ...@@ -43,21 +34,18 @@ def pkgbase_exists(pkgbase):
def list_repos(user): def list_repos(user):
db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, conn = db.Connection()
passwd=aur_db_pass, db=aur_db_name,
unix_socket=aur_db_socket)
cur = db.cursor()
cur.execute("SELECT ID FROM Users WHERE Username = %s ", [user]) cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user])
userid = cur.fetchone()[0] userid = cur.fetchone()[0]
if userid == 0: if userid == 0:
die('{:s}: unknown user: {:s}'.format(action, user)) die('{:s}: unknown user: {:s}'.format(action, user))
cur.execute("SELECT Name, PackagerUID FROM PackageBases " + cur = conn.execute("SELECT Name, PackagerUID FROM PackageBases " +
"WHERE MaintainerUID = %s ", [userid]) "WHERE MaintainerUID = ?", [userid])
for row in cur: for row in cur:
print((' ' if row[1] else '*') + row[0]) print((' ' if row[1] else '*') + row[0])
db.close() conn.close()
def create_pkgbase(pkgbase, user): def create_pkgbase(pkgbase, user):
...@@ -66,26 +54,25 @@ def create_pkgbase(pkgbase, user): ...@@ -66,26 +54,25 @@ def create_pkgbase(pkgbase, user):
if pkgbase_exists(pkgbase): if pkgbase_exists(pkgbase):
die('{:s}: package base already exists: {:s}'.format(action, pkgbase)) die('{:s}: package base already exists: {:s}'.format(action, pkgbase))
db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, conn = db.Connection()
passwd=aur_db_pass, db=aur_db_name,
unix_socket=aur_db_socket)
cur = db.cursor()
cur.execute("SELECT ID FROM Users WHERE Username = %s ", [user]) cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user])
userid = cur.fetchone()[0] userid = cur.fetchone()[0]
if userid == 0: if userid == 0:
die('{:s}: unknown user: {:s}'.format(action, user)) die('{:s}: unknown user: {:s}'.format(action, user))
cur.execute("INSERT INTO PackageBases (Name, SubmittedTS, ModifiedTS, " + cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " +
"SubmitterUID, MaintainerUID) VALUES (%s, UNIX_TIMESTAMP(), " + "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " +
"UNIX_TIMESTAMP(), %s, %s)", [pkgbase, userid, userid]) "(?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), ?, ?)",
[pkgbase, userid, userid])
pkgbase_id = cur.lastrowid pkgbase_id = cur.lastrowid
cur.execute("INSERT INTO PackageNotifications (PackageBaseID, UserID) " + cur = conn.execute("INSERT INTO PackageNotifications " +
"VALUES (%s, %s)", [pkgbase_id, userid]) "(PackageBaseID, UserID) VALUES (?, ?)",
[pkgbase_id, userid])
db.commit() conn.commit()
db.close() conn.close()
def pkgbase_set_keywords(pkgbase, keywords): def pkgbase_set_keywords(pkgbase, keywords):
...@@ -93,34 +80,29 @@ def pkgbase_set_keywords(pkgbase, keywords): ...@@ -93,34 +80,29 @@ def pkgbase_set_keywords(pkgbase, keywords):
if not pkgbase_id: if not pkgbase_id:
die('{:s}: package base not found: {:s}'.format(action, pkgbase)) die('{:s}: package base not found: {:s}'.format(action, pkgbase))
db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, conn = db.Connection()
passwd=aur_db_pass, db=aur_db_name,
unix_socket=aur_db_socket)
cur = db.cursor()
cur.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = %s", conn.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?",
[pkgbase_id]) [pkgbase_id])
for keyword in keywords: for keyword in keywords:
cur.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " conn.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " +
"VALUES (%s, %s)", [pkgbase_id, keyword]) "VALUES (?, ?)", [pkgbase_id, keyword])
db.commit() conn.commit()
db.close() conn.close()
def pkgbase_has_write_access(pkgbase, user): def pkgbase_has_write_access(pkgbase, user):
db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, conn = db.Connection()
passwd=aur_db_pass, db=aur_db_name,
unix_socket=aur_db_socket, buffered=True)
cur = db.cursor()
cur.execute("SELECT COUNT(*) FROM PackageBases " + cur = conn.execute("SELECT COUNT(*) FROM PackageBases " +
"LEFT JOIN PackageComaintainers " + "LEFT JOIN PackageComaintainers " +
"ON PackageComaintainers.PackageBaseID = PackageBases.ID " + "ON PackageComaintainers.PackageBaseID = PackageBases.ID " +
"INNER JOIN Users ON Users.ID = PackageBases.MaintainerUID " + "INNER JOIN Users " +
"ON Users.ID = PackageBases.MaintainerUID " +
"OR PackageBases.MaintainerUID IS NULL " + "OR PackageBases.MaintainerUID IS NULL " +
"OR Users.ID = PackageComaintainers.UsersID " + "OR Users.ID = PackageComaintainers.UsersID " +
"WHERE Name = %s AND Username = %s", [pkgbase, user]) "WHERE Name = ? AND Username = ?", [pkgbase, user])
return cur.fetchone()[0] > 0 return cur.fetchone()[0] > 0
......
#!/usr/bin/python3 #!/usr/bin/python3
import configparser import configparser
import mysql.connector
import os import os
import pygit2 import pygit2
import re import re
...@@ -11,15 +10,11 @@ import sys ...@@ -11,15 +10,11 @@ import sys
import srcinfo.parse import srcinfo.parse
import srcinfo.utils import srcinfo.utils
import db
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config")
aur_db_host = config.get('database', 'host')
aur_db_name = config.get('database', 'name')
aur_db_user = config.get('database', 'user')
aur_db_pass = config.get('database', 'password')
aur_db_socket = config.get('database', 'socket')
notify_cmd = config.get('notifications', 'notify-cmd') notify_cmd = config.get('notifications', 'notify-cmd')
repo_path = config.get('serve', 'repo-path') repo_path = config.get('serve', 'repo-path')
...@@ -65,26 +60,26 @@ def parse_dep(depstring): ...@@ -65,26 +60,26 @@ def parse_dep(depstring):
return (depname, depcond) return (depname, depcond)
def save_metadata(metadata, db, cur, user): def save_metadata(metadata, conn, user):
# Obtain package base ID and previous maintainer. # Obtain package base ID and previous maintainer.
pkgbase = metadata['pkgbase'] pkgbase = metadata['pkgbase']
cur.execute("SELECT ID, MaintainerUID FROM PackageBases " cur = conn.execute("SELECT ID, MaintainerUID FROM PackageBases "
"WHERE Name = %s", [pkgbase]) "WHERE Name = ?", [pkgbase])
(pkgbase_id, maintainer_uid) = cur.fetchone() (pkgbase_id, maintainer_uid) = cur.fetchone()
was_orphan = not maintainer_uid was_orphan = not maintainer_uid
# Obtain the user ID of the new maintainer. # Obtain the user ID of the new maintainer.
cur.execute("SELECT ID FROM Users WHERE Username = %s", [user]) cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user])
user_id = int(cur.fetchone()[0]) user_id = int(cur.fetchone()[0])
# Update package base details and delete current packages. # Update package base details and delete current packages.
cur.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " + conn.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " +
"PackagerUID = %s, OutOfDateTS = NULL WHERE ID = %s", "PackagerUID = ?, OutOfDateTS = NULL WHERE ID = ?",
[user_id, pkgbase_id]) [user_id, pkgbase_id])
cur.execute("UPDATE PackageBases SET MaintainerUID = %s " + conn.execute("UPDATE PackageBases SET MaintainerUID = ? " +
"WHERE ID = %s AND MaintainerUID IS NULL", "WHERE ID = ? AND MaintainerUID IS NULL",
[user_id, pkgbase_id]) [user_id, pkgbase_id])
cur.execute("DELETE FROM Packages WHERE PackageBaseID = %s", conn.execute("DELETE FROM Packages WHERE PackageBaseID = ?",
[pkgbase_id]) [pkgbase_id])
for pkgname in srcinfo.utils.get_package_names(metadata): for pkgname in srcinfo.utils.get_package_names(metadata):
...@@ -102,94 +97,94 @@ def save_metadata(metadata, db, cur, user): ...@@ -102,94 +97,94 @@ def save_metadata(metadata, db, cur, user):
pkginfo[field] = None pkginfo[field] = None
# Create a new package. # Create a new package.
cur.execute("INSERT INTO Packages (PackageBaseID, Name, " + cur = conn.execute("INSERT INTO Packages (PackageBaseID, Name, " +
"Version, Description, URL) " + "Version, Description, URL) " +
"VALUES (%s, %s, %s, %s, %s)", "VALUES (?, ?, ?, ?, ?)",
[pkgbase_id, pkginfo['pkgname'], ver, [pkgbase_id, pkginfo['pkgname'], ver,
pkginfo['pkgdesc'], pkginfo['url']]) pkginfo['pkgdesc'], pkginfo['url']])
db.commit() conn.commit()
pkgid = cur.lastrowid pkgid = cur.lastrowid
# Add package sources. # Add package sources.
for source_info in extract_arch_fields(pkginfo, 'source'): for source_info in extract_arch_fields(pkginfo, 'source'):
cur.execute("INSERT INTO PackageSources (PackageID, Source, " + conn.execute("INSERT INTO PackageSources (PackageID, Source, " +
"SourceArch) VALUES (%s, %s, %s)", "SourceArch) VALUES (?, ?, ?)",
[pkgid, source_info['value'], source_info['arch']]) [pkgid, source_info['value'], source_info['arch']])
# Add package dependencies. # Add package dependencies.
for deptype in ('depends', 'makedepends', for deptype in ('depends', 'makedepends',
'checkdepends', 'optdepends'): 'checkdepends', 'optdepends'):
cur.execute("SELECT ID FROM DependencyTypes WHERE Name = %s", cur = conn.execute("SELECT ID FROM DependencyTypes WHERE Name = ?",
[deptype]) [deptype])
deptypeid = cur.fetchone()[0] deptypeid = cur.fetchone()[0]
for dep_info in extract_arch_fields(pkginfo, deptype): for dep_info in extract_arch_fields(pkginfo, deptype):
depname, depcond = parse_dep(dep_info['value']) depname, depcond = parse_dep(dep_info['value'])
deparch = dep_info['arch'] deparch = dep_info['arch']
cur.execute("INSERT INTO PackageDepends (PackageID, " + conn.execute("INSERT INTO PackageDepends (PackageID, " +
"DepTypeID, DepName, DepCondition, DepArch) " + "DepTypeID, DepName, DepCondition, DepArch) " +
"VALUES (%s, %s, %s, %s, %s)", "VALUES (?, ?, ?, ?, ?)",
[pkgid, deptypeid, depname, depcond, deparch]) [pkgid, deptypeid, depname, depcond, deparch])
# Add package relations (conflicts, provides, replaces). # Add package relations (conflicts, provides, replaces).
for reltype in ('conflicts', 'provides', 'replaces'): for reltype in ('conflicts', 'provides', 'replaces'):
cur.execute("SELECT ID FROM RelationTypes WHERE Name = %s", cur = conn.execute("SELECT ID FROM RelationTypes WHERE Name = ?",
[reltype]) [reltype])
reltypeid = cur.fetchone()[0] reltypeid = cur.fetchone()[0]
for rel_info in extract_arch_fields(pkginfo, reltype): for rel_info in extract_arch_fields(pkginfo, reltype):
relname, relcond = parse_dep(rel_info['value']) relname, relcond = parse_dep(rel_info['value'])
relarch = rel_info['arch'] relarch = rel_info['arch']
cur.execute("INSERT INTO PackageRelations (PackageID, " + conn.execute("INSERT INTO PackageRelations (PackageID, " +
"RelTypeID, RelName, RelCondition, RelArch) " + "RelTypeID, RelName, RelCondition, RelArch) " +
"VALUES (%s, %s, %s, %s, %s)", "VALUES (?, ?, ?, ?, ?)",
[pkgid, reltypeid, relname, relcond, relarch]) [pkgid, reltypeid, relname, relcond, relarch])
# Add package licenses. # Add package licenses.
if 'license' in pkginfo: if 'license' in pkginfo:
for license in pkginfo['license']: for license in pkginfo['license']:
cur.execute("SELECT ID FROM Licenses WHERE Name = %s", cur = conn.execute("SELECT ID FROM Licenses WHERE Name = ?",
[license]) [license])
if cur.rowcount == 1: if cur.rowcount == 1:
licenseid = cur.fetchone()[0] licenseid = cur.fetchone()[0]
else: else:
cur.execute("INSERT INTO Licenses (Name) VALUES (%s)", cur = conn.execute("INSERT INTO Licenses (Name) " +
[license]) "VALUES (?)", [license])
db.commit() conn.commit()
licenseid = cur.lastrowid licenseid = cur.lastrowid
cur.execute("INSERT INTO PackageLicenses (PackageID, " + conn.execute("INSERT INTO PackageLicenses (PackageID, " +
"LicenseID) VALUES (%s, %s)", "LicenseID) VALUES (?, ?)",
[pkgid, licenseid]) [pkgid, licenseid])
# Add package groups. # Add package groups.
if 'groups' in pkginfo: if 'groups' in pkginfo:
for group in pkginfo['groups']: for group in pkginfo['groups']:
cur.execute("SELECT ID FROM Groups WHERE Name = %s", cur = conn.execute("SELECT ID FROM Groups WHERE Name = ?",
[group]) [group])
if cur.rowcount == 1: if cur.rowcount == 1:
groupid = cur.fetchone()[0] groupid = cur.fetchone()[0]
else: else:
cur.execute("INSERT INTO Groups (Name) VALUES (%s)", cur = conn.execute("INSERT INTO Groups (Name) VALUES (?)",
[group]) [group])
db.commit() conn.commit()
groupid = cur.lastrowid groupid = cur.lastrowid
cur.execute("INSERT INTO PackageGroups (PackageID, " conn.execute("INSERT INTO PackageGroups (PackageID, "
"GroupID) VALUES (%s, %s)", [pkgid, groupid]) "GroupID) VALUES (?, ?)", [pkgid, groupid])
# Add user to notification list on adoption. # Add user to notification list on adoption.
if was_orphan: if was_orphan:
cur.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + cur = conn.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " +
"PackageBaseID = %s AND UserID = %s", "PackageBaseID = ? AND UserID = ?",
[pkgbase_id, user_id]) [pkgbase_id, user_id])
if cur.fetchone()[0] == 0: if cur.fetchone()[0] == 0:
cur.execute("INSERT INTO PackageNotifications " + conn.execute("INSERT INTO PackageNotifications " +
"(PackageBaseID, UserID) VALUES (%s, %s)", "(PackageBaseID, UserID) VALUES (?, ?)",
[pkgbase_id, user_id]) [pkgbase_id, user_id])
db.commit() conn.commit()
def update_notify(db, cur, user, pkgbase_id): def update_notify(conn, user, pkgbase_id):
# Obtain the user ID of the new maintainer. # Obtain the user ID of the new maintainer.
cur.execute("SELECT ID FROM Users WHERE Username = %s", [user]) cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user])
user_id = int(cur.fetchone()[0]) user_id = int(cur.fetchone()[0])
# Execute the notification script. # Execute the notification script.
...@@ -233,10 +228,7 @@ else: ...@@ -233,10 +228,7 @@ else:
if refname != "refs/heads/master": if refname != "refs/heads/master":
die("pushing to a branch other than master is restricted") die("pushing to a branch other than master is restricted")
db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, conn = db.Connection()
passwd=aur_db_pass, db=aur_db_name,
unix_socket=aur_db_socket, buffered=True)
cur = db.cursor()
# Detect and deny non-fast-forwards. # Detect and deny non-fast-forwards.
if sha1_old != "0000000000000000000000000000000000000000" and not privileged: if sha1_old != "0000000000000000000000000000000000000000" and not privileged:
...@@ -339,13 +331,13 @@ if metadata_pkgbase != pkgbase: ...@@ -339,13 +331,13 @@ if metadata_pkgbase != pkgbase:
# Ensure that packages are neither blacklisted nor overwritten. # Ensure that packages are neither blacklisted nor overwritten.
pkgbase = metadata['pkgbase'] pkgbase = metadata['pkgbase']
cur.execute("SELECT ID FROM PackageBases WHERE Name = %s", [pkgbase]) cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase])
pkgbase_id = cur.fetchone()[0] if cur.rowcount == 1 else 0 pkgbase_id = cur.fetchone()[0] if cur.rowcount == 1 else 0
cur.execute("SELECT Name FROM PackageBlacklist") cur = conn.execute("SELECT Name FROM PackageBlacklist")
blacklist = [row[0] for row in cur.fetchall()] blacklist = [row[0] for row in cur.fetchall()]
cur.execute("SELECT Name, Repo FROM OfficialProviders") cur = conn.execute("SELECT Name, Repo FROM OfficialProviders")
providers = dict(cur.fetchall()) providers = dict(cur.fetchall())
for pkgname in srcinfo.utils.get_package_names(metadata): for pkgname in srcinfo.utils.get_package_names(metadata):
...@@ -358,13 +350,13 @@ for pkgname in srcinfo.utils.get_package_names(metadata): ...@@ -358,13 +350,13 @@ for pkgname in srcinfo.utils.get_package_names(metadata):
repo = providers[pkgname] repo = providers[pkgname]
warn_or_die('package already provided by [{:s}]: {:s}'.format(repo, pkgname)) warn_or_die('package already provided by [{:s}]: {:s}'.format(repo, pkgname))
cur.execute("SELECT COUNT(*) FROM Packages WHERE Name = %s AND " + cur = conn.execute("SELECT COUNT(*) FROM Packages WHERE Name = ? AND " +