diff options
author | Daniele Varrazzo <daniele.varrazzo@gmail.com> | 2018-06-17 22:39:10 +0100 |
---|---|---|
committer | Daniele Varrazzo <daniele.varrazzo@gmail.com> | 2018-08-17 02:00:40 +0100 |
commit | 782fa39647a79bc0269917cd9f539c793c13774a (patch) | |
tree | 2e45929ddf80db4af1b8843695b09b73d5aa3317 /scripts/make_errors.py | |
parent | 0bce58d0cdadb6756bb6f999cbae4f47feb49b95 (diff) |
Generating the whole errors file from script
Diffstat (limited to 'scripts/make_errors.py')
-rwxr-xr-x | scripts/make_errors.py | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/scripts/make_errors.py b/scripts/make_errors.py new file mode 100755 index 00000000..855ca35c --- /dev/null +++ b/scripts/make_errors.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +"""Generate the errors module from PostgreSQL source code. + +The script can be run at a new PostgreSQL release to refresh the module. +""" + +# Copyright (C) 2018 Daniele Varrazzo <daniele.varrazzo@gmail.com> +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +from __future__ import print_function + +import re +import sys +import urllib2 +from collections import defaultdict + + +def main(): + if len(sys.argv) != 2: + print("usage: %s /path/to/errors.py" % sys.argv[0], file=sys.stderr) + return 2 + + filename = sys.argv[1] + + file_start = read_base_file(filename) + # If you add a version to the list fix the docs (in errors.rst) + classes, errors = fetch_errors( + ['9.1', '9.2', '9.3', '9.4', '9.5', '9.6', '10']) + + f = open(filename, "w") + for line in file_start: + print(line, file=f) + for line in generate_module_data(classes, errors): + print(line, file=f) + + +def read_base_file(filename): + rv = [] + for line in open(filename): + rv.append(line.rstrip("\n")) + if line.startswith("# autogenerated"): + return rv + + raise ValueError("can't find the separator. Is this the right file?") + + +def parse_errors_txt(url): + classes = {} + errors = defaultdict(dict) + + page = urllib2.urlopen(url) + for line in page: + # Strip comments and skip blanks + line = line.split('#')[0].strip() + if not line: + continue + + # Parse a section + m = re.match(r"Section: (Class (..) - .+)", line) + if m: + label, class_ = m.groups() + classes[class_] = label + continue + + # Parse an error + m = re.match(r"(.....)\s+(?:E|W|S)\s+ERRCODE_(\S+)(?:\s+(\S+))?$", line) + if m: + errcode, macro, spec = m.groups() + # skip errcodes without specs as they are not publically visible + if not spec: + continue + errlabel = spec.upper() + errors[class_][errcode] = errlabel + continue + + # We don't expect anything else + raise ValueError("unexpected line:\n%s" % line) + + return classes, errors + + +errors_txt_url = \ + "http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob_plain;" \ + "f=src/backend/utils/errcodes.txt;hb=%s" + + +def fetch_errors(versions): + classes = {} + errors = defaultdict(dict) + + for version in versions: + print(version, file=sys.stderr) + tver = tuple(map(int, version.split()[0].split('.'))) + tag = '%s%s_STABLE' % ( + (tver[0] >= 10 and 'REL_' or 'REL'), + version.replace('.', '_')) + c1, e1 = parse_errors_txt(errors_txt_url % tag) + classes.update(c1) + + for c, cerrs in e1.items(): + errors[c].update(cerrs) + + return classes, errors + + +def generate_module_data(classes, errors): + tmpl = """ + +class %(cls)s(%(base)s): + pass + +_by_sqlstate[%(errcode)r] = %(cls)s\ +""" + for clscode, clslabel in sorted(classes.items()): + if clscode in ('00', '01'): + # success and warning - never raised + continue + + yield "\n\n# %s" % clslabel + + for errcode, errlabel in sorted(errors[clscode].items()): + clsname = errlabel.title().replace('_', '') + yield tmpl % { + 'cls': clsname, + 'base': get_base_class_name(errcode), + 'errcode': errcode + } + + +def get_base_class_name(errcode): + """ + This is a python porting of exception_from_sqlstate code in pqpath.c + """ + if errcode[0] == '0': + if errcode[1] == 'A': # Class 0A - Feature Not Supported + return 'NotSupportedError' + elif errcode[0] == '2': + if errcode[1] in '01': + # Class 20 - Case Not Found + # Class 21 - Cardinality Violation + return 'ProgrammingError' + elif errcode[1] == '2': # Class 22 - Data Exception + return 'DataError' + elif errcode[1] == '3': # Class 23 - Integrity Constraint Violation + return 'IntegrityError' + elif errcode[1] in '45': + # Class 24 - Invalid Cursor State + # Class 25 - Invalid Transaction State + return 'InternalError' + elif errcode[1] in '678': + # Class 26 - Invalid SQL Statement Name + # Class 27 - Triggered Data Change Violation + # Class 28 - Invalid Authorization Specification + return 'OperationalError' + elif errcode[1] in 'BDF': + # Class 2B - Dependent Privilege Descriptors Still Exist + # Class 2D - Invalid Transaction Termination + # Class 2F - SQL Routine Exception + return 'InternalError' + elif errcode[0] == '3': + if errcode[1] == '4': # Class 34 - Invalid Cursor Name + return 'OperationalError' + if errcode[1] in '89B': + # Class 38 - External Routine Exception + # Class 39 - External Routine Invocation Exception + # Class 3B - Savepoint Exception + return 'InternalError' + if errcode[1] in 'DF': + # Class 3D - Invalid Catalog Name + # Class 3F - Invalid Schema Name + return 'ProgrammingError' + elif errcode[0] == '4': + if errcode[1] == '0': # Class 40 - Transaction Rollback + return 'TransactionRollbackError' + if errcode[1] in '24': + # Class 42 - Syntax Error or Access Rule Violation + # Class 44 - WITH CHECK OPTION Violation + return 'ProgrammingError' + elif errcode[0] == '5': + if errcode == "57014": + return 'QueryCanceledError' + # Class 53 - Insufficient Resources + # Class 54 - Program Limit Exceeded + # Class 55 - Object Not In Prerequisite State + # Class 57 - Operator Intervention + # Class 58 - System Error (errors external to PostgreSQL itself) + else: + return 'OperationalError' + elif errcode[0] == 'F': # Class F0 - Configuration File Error + return 'InternalError' + elif errcode[0] == 'H': # Class HV - Foreign Data Wrapper Error (SQL/MED) + return 'OperationalError' + elif errcode[0] == 'P': # Class P0 - PL/pgSQL Error + return 'InternalError' + elif errcode[0] == 'X': # Class XX - Internal Error + return 'InternalError' + + return 'DatabaseError' + + +if __name__ == '__main__': + sys.exit(main()) |