Skip to content

Commit de12a95

Browse files
committed
Add custom exception types
1 parent 729e723 commit de12a95

File tree

8 files changed

+944
-26
lines changed

8 files changed

+944
-26
lines changed

deps/error_codes.jl

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using EzXML
2+
using HTTP
3+
4+
const ERROR_CODE_APPENDIX = "https://www.postgresql.org/docs/current/errcodes-appendix.html"
5+
6+
pascalcase(str) = replace(titlecase(str), '_' => "")
7+
8+
const BASE_CODE = """# autogenerated by deps/error_codes.jl using $ERROR_CODE_APPENDIX
9+
struct Class
10+
chars::NTuple{2, Char}
11+
end
12+
13+
Class(str::String) = Class(NTuple{2, Char}(codeunits(str)))
14+
15+
macro c_str(str)
16+
Class(str)
17+
end
18+
19+
function Base.show(io::IO, c::Class)
20+
print(io, "c\\\"", c.chars..., '"')
21+
end
22+
23+
struct ErrorCode
24+
chars::NTuple{5, Char}
25+
end
26+
27+
ErrorCode(str::String) = ErrorCode(NTuple{5, Char}(codeunits(str)))
28+
29+
macro e_str(str)
30+
ErrorCode(str)
31+
end
32+
33+
function Base.show(io::IO, e::ErrorCode)
34+
print(io, "e\\\"", e.chars..., '"')
35+
end
36+
"""
37+
38+
eval(Meta.parse("""
39+
begin
40+
$BASE_CODE
41+
end"""))
42+
43+
# --------------
44+
45+
const SUCCESS_CLASS = Class("00")
46+
const WARNING_CLASSES = (Class("01"), Class("02"))
47+
const EXTERNAL_CLASSES = (Class("38"), Class("39"))
48+
49+
error_code_html(url=ERROR_CODE_APPENDIX) = String(HTTP.get(url))
50+
51+
function error_code_table(html)
52+
parsed = parsehtml(html)
53+
54+
return findfirst("//table[@summary='PostgreSQL Error Codes']", parsed)
55+
end
56+
57+
parse_row(node) = map(nodecontent, findall("td/code", node))
58+
59+
function generate_error_codes(io, html=error_code_html())
60+
table = error_code_table(html)
61+
rows = findall("tbody/tr/td/code/../..", table)
62+
id_names = Set{String}()
63+
64+
error_names_io = IOBuffer()
65+
66+
println(error_names_io, "\n\nconst ERROR_NAMES = Dict(")
67+
68+
print(io, BASE_CODE)
69+
70+
for row in rows
71+
code, name = parse_row(row)
72+
73+
id_name = pascalcase(name)
74+
class = Class(code)
75+
error_code = ErrorCode(code)
76+
77+
if endswith(code, "000")
78+
suffix = class in (SUCCESS_CLASS, WARNING_CLASSES...) ? "Class" : "ErrorClass"
79+
80+
println(io, "\n\nconst $(id_name)$(suffix) = PQResultError{$class}\n")
81+
end
82+
83+
if class in WARNING_CLASSES
84+
id_name *= "Warning"
85+
end
86+
87+
if id_name in id_names
88+
if class in EXTERNAL_CLASSES
89+
id_name *= "Ext" # like psycopg2
90+
else
91+
error("Duplicate identifier: $id_name")
92+
end
93+
end
94+
95+
push!(id_names, id_name)
96+
97+
println(io, "const $(id_name) = PQResultError{$class, $error_code}")
98+
println(error_names_io, " $id_name => \"$id_name\",")
99+
end
100+
println(error_names_io, ")")
101+
102+
seekstart(error_names_io)
103+
write(io, error_names_io)
104+
105+
return
106+
end
107+
108+
function main()
109+
if length(ARGS) > 0
110+
open(ARGS[1], "w") do fp
111+
generate_error_codes(fp)
112+
end
113+
else
114+
generate_error_codes(stdout)
115+
end
116+
end
117+
118+
abspath(PROGRAM_FILE) == @__FILE__() && main()

src/LibPQ.jl

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ const LIBPQ_CONVERSIONS = PQConversions()
7979
include("connections.jl")
8080
include("results.jl")
8181
include("statements.jl")
82+
include("exceptions.jl")
8283

8384
include("parsing.jl")
8485
include("copy.jl")

src/connections.jl

+18-9
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ returned `Connection`.
9393
"""
9494
function handle_new_connection(jl_conn::Connection; throw_error::Bool=true)
9595
if status(jl_conn) == libpq_c.CONNECTION_BAD
96-
err = error_message(jl_conn)
96+
err = PQConnectionError(jl_conn)
9797

9898
if throw_error
9999
close(jl_conn)
@@ -351,7 +351,10 @@ function encoding(jl_conn::Connection)
351351
encoding_id::Cint = libpq_c.PQclientEncoding(jl_conn.conn)
352352

353353
if encoding_id == -1
354-
error(LOGGER, "libpq could not retrieve the connection's client encoding")
354+
error(LOGGER, JLConnectionError(
355+
"libpq could not retrieve the connection's client encoding. " *
356+
"Something is wrong with the connection."
357+
))
355358
end
356359

357360
return unsafe_string(libpq_c.pg_encoding_to_char(encoding_id))
@@ -375,9 +378,9 @@ function set_encoding!(jl_conn::Connection, encoding::String)
375378
status = libpq_c.PQsetClientEncoding(jl_conn.conn, encoding)
376379

377380
if status == -1
378-
error(LOGGER,
381+
error(LOGGER, JLConnectionError(
379382
"libpq could not set the connection's client encoding to $encoding"
380-
)
383+
))
381384
else
382385
jl_conn.encoding = encoding
383386
end
@@ -525,7 +528,7 @@ function Base.parse(::Type{ConninfoDisplay}, str::AbstractString)::ConninfoDispl
525528
elseif first(str) == 'D'
526529
Debug
527530
else
528-
error(LOGGER, "Unexpected dispchar '$str' in PQconninfoOption")
531+
error(LOGGER, JLConnectionError("Unexpected dispchar '$str' in PQconninfoOption"))
529532
end
530533
end
531534

@@ -580,9 +583,13 @@ function conninfo(jl_conn::Connection)
580583

581584
if ci_ptr == C_NULL
582585
if !isopen(jl_conn)
583-
error(LOGGER, "Connection is closed")
586+
error(LOGGER, JLConnectionError(
587+
"Cannot get connection info as the connection is closed."
588+
))
584589
else
585-
error(LOGGER, "libpq could not allocate memory for connection info")
590+
error(LOGGER, JLConnectionError(
591+
"libpq could not allocate memory for connection info"
592+
))
586593
end
587594
end
588595

@@ -618,13 +625,15 @@ function conninfo(str::AbstractString)
618625
ci_ptr = libpq_c.PQconninfoParse(str, err_ref)
619626

620627
if ci_ptr == C_NULL && err_ref[] == C_NULL
621-
error(LOGGER, "libpq could not allocate memory for connection info")
628+
error(LOGGER, JLConnectionError(
629+
"libpq could not allocate memory for connection info"
630+
))
622631
end
623632

624633
if err_ref[] != C_NULL
625634
err_msg = unsafe_string(err_ref[])
626635
libpq_c.PQfreemem(err_ref[])
627-
error(err_msg)
636+
error(LOGGER, ConninfoParseError(err_msg))
628637
end
629638

630639
ci_array = conninfo(ci_ptr)

0 commit comments

Comments
 (0)