Skip to content

Commit e8f4724

Browse files
authored
Fix calling stored procedures that return resultsets (#154)
Fixes #149. The issue here is that calling stored procedures that return resultsets return the resultset, and then an extra, empty resultset (who knows why, but whatever). In general, we weren't handling when queries returned multiple resultsets anyway, so this was a more general problem. We now will return the first resultset as the official resultset and clear out any additional resultsets if they exist. We should definitely come up with a better api if people actually need/want multiple resultsets, but that's a problem for another day. It turns out we also had problems returning resultsets from prepared statements, which is the reason for the extra tests and code changes in prepare.jl.
1 parent 5e45c84 commit e8f4724

File tree

6 files changed

+65
-7
lines changed

6 files changed

+65
-7
lines changed

src/MySQL.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ end
4747

4848
function clear!(conn, result::API.MYSQL_RES)
4949
if result.ptr != C_NULL
50-
while API.fetchrow(conn.mysql, result) != C_NULL
50+
while API.fetchrow(conn.mysql, result) != C_NULL || API.nextresult(conn.mysql) !== nothing
5151
end
5252
finalize(result)
5353
end
@@ -56,7 +56,7 @@ end
5656

5757
function clear!(conn, stmt::API.MYSQL_STMT)
5858
if stmt.ptr != C_NULL
59-
while API.fetch(stmt) == 0
59+
while API.fetch(stmt) == 0 || API.nextresult(stmt) !== nothing
6060
end
6161
end
6262
return

src/api/capi.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ Return Value Description
662662
"""=#
663663
function nextresult(mysql::MYSQL)
664664
ret = mysql_next_result(mysql.ptr)
665-
return ret == -1 ? nothing : reg == 0 ? ret : throw(Error(mysql))
665+
return ret == -1 ? nothing : ret == 0 ? ret : throw(Error(mysql))
666666
end
667667

668668
#="""

src/api/papi.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,8 @@ Return Value Description
272272
273273
"""
274274
function nextresult(stmt::MYSQL_STMT)
275-
return mysql_stmt_next_result(stmt.ptr)
275+
ret = mysql_stmt_next_result(stmt.ptr)
276+
return ret == -1 ? nothing : ret == 0 ? ret : throw(StmtError(stmt))
276277
end
277278

278279
"""

src/execute.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,14 @@ function Base.iterate(cursor::TextCursor{buffered}, i=1) where {buffered}
9191
rowptr = API.fetchrow(cursor.conn.mysql, cursor.result)
9292
if rowptr == C_NULL
9393
!buffered && API.errno(cursor.conn.mysql) != 0 && throw(API.Error(cursor.conn.mysql))
94-
finalize(cursor.result)
95-
return nothing
94+
if API.nextresult(cursor.conn.mysql) === nothing
95+
finalize(cursor.result)
96+
return nothing
97+
else
98+
# we ***ignore*** additional resultsets for now
99+
clear!(cursor.conn)
100+
return nothing
101+
end
96102
end
97103
lengths = API.fetchlengths(cursor.result, cursor.nfields)
98104
cursor.current_rownumber = i

src/prepare.jl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ freeing resources, it has been noted that too many unclosed statements and resul
4141
with streaming queries (i.e. `mysql_store_result=false`) has led to occasional resultset corruption.
4242
"""
4343
function DBInterface.prepare(conn::Connection, sql::AbstractString)
44+
clear!(conn)
4445
stmt = API.stmtinit(conn.mysql)
4546
API.prepare(stmt, sql)
4647
nparams = API.paramcount(stmt)
@@ -166,7 +167,29 @@ function DBInterface.execute(stmt::Statement, params=(); mysql_store_result::Boo
166167
buffered = true
167168
rows = API.numrows(stmt.stmt)
168169
end
169-
return Cursor{buffered}(stmt.stmt, stmt.nfields, stmt.names, stmt.types, stmt.lookup, stmt.valuehelpers, stmt.values, rows_affected, rows, 0)
170+
nfields = stmt.nfields
171+
names = stmt.names
172+
types = stmt.types
173+
valuehelpers = stmt.valuehelpers
174+
values = stmt.values
175+
lookup = stmt.lookup
176+
if stmt.nfields == 0
177+
nfields = API.fieldcount(stmt.stmt)
178+
result = API.resultmetadata(stmt.stmt)
179+
if result.ptr != C_NULL
180+
fields = API.fetchfields(result, nfields)
181+
names = [ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Csize_t), x.name, x.name_length) for x in fields]
182+
types = [juliatype(x.field_type, API.notnullable(x), API.isunsigned(x), API.isbinary(x)) for x in fields]
183+
valuehelpers = [API.BindHelper() for i = 1:nfields]
184+
values = [API.MYSQL_BIND(valuehelpers[i].length, valuehelpers[i].is_null) for i = 1:nfields]
185+
foreach(1:nfields) do i
186+
returnbind!(valuehelpers[i], values, i, fields[i].field_type, types[i])
187+
end
188+
API.bindresult(stmt.stmt, values)
189+
lookup = Dict(x => i for (i, x) in enumerate(names))
190+
end
191+
end
192+
return Cursor{buffered}(stmt.stmt, nfields, names, types, lookup, valuehelpers, values, rows_affected, rows, 0)
170193
end
171194

172195
inithelper!(helper, x::Missing) = nothing

test/runtests.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,31 @@ DBInterface.execute(stmt, [-1, "hey there sailor"])
221221
res = DBInterface.execute(conn, "select id, t from blob_field") |> columntable
222222
@test length(res) == 2
223223
@test res[2][1] == [0x68, 0x65, 0x79, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x20, 0x73, 0x61, 0x69, 0x6c, 0x6f, 0x72]
224+
225+
226+
DBInterface.execute(conn, """
227+
CREATE PROCEDURE get_employee()
228+
BEGIN
229+
select * from Employee;
230+
END
231+
""")
232+
res = DBInterface.execute(conn, "call get_employee()") |> columntable
233+
@test length(res) > 0
234+
@test length(res[1]) == 5
235+
res = DBInterface.execute(conn, "call get_employee()") |> columntable
236+
@test length(res) > 0
237+
@test length(res[1]) == 5
238+
# test that we can call multiple stored procedures in a row w/o collecting results (they get cleaned up properly internally)
239+
res = DBInterface.execute(conn, "call get_employee()")
240+
res = DBInterface.execute(conn, "call get_employee()")
241+
242+
# and for prepared statements
243+
stmt = DBInterface.prepare(conn, "call get_employee()")
244+
res = DBInterface.execute(stmt) |> columntable
245+
@test length(res) > 0
246+
@test length(res[1]) == 5
247+
res = DBInterface.execute(stmt) |> columntable
248+
@test length(res) > 0
249+
@test length(res[1]) == 5
250+
res = DBInterface.execute(stmt)
251+
res = DBInterface.execute(stmt)

0 commit comments

Comments
 (0)