You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduce a new DateAndTime sturct for > millisecond precision support (#178)
Fixes#165 (and #172). So the proposal here is a bit tricky, but here's
what's going on in this PR:
* Introduce a new DateAndTime struct, which just contains separate
`Date` and `Time` fields, where the `time` field can support up to
Nanosecond precision
* For _prepared_ statement execution, we actually return a DateTime
column for > millisecond precision, and there used to be no warning.
We now emit a warning that precision is being lost and that
`mysql_date_and_time=true` should be passed to avoid such loss
* For _directly executed_ queries, we throw an InexactError and now
emit the same warning that `mysql_date_and_time=true` should be passed
to avoid such errors. We could perhaps match the prepare behavior and
return `DateTime`, but I wanted to avoid changing too much all at once
* Why introduce a keyword arg instead of just making it the default?
Well, that'd be breaking. It's also tricky because we don't
necessarily want to force this new `DateAndTime` struct on _all_
DATETIME/TIMESTAMP columns, especially if their precision is within
the millisecond range. i.e. it's much more convenient for users to
work direclty with `DateTime` objects instead of this new
`DateAndTime` struct. So the behavior now is: emit clear warnings in
cases where DateTime can't handle the precision, and users can pass
`mysql_date_and_time=true` and then handle `DateAndTime` objects
accordingly.
@noinlinedateandtime_warning() =@warn"""a datetime value from a column has a microsecond precision > 3,
120
+
by default, MySQL.jl attempts to return a DateTime object, which only supports millisecond precision.
121
+
To avoid loss in precision or InexactErrors, pass `mysql_date_and_time=true` to `DBInterface.execute(stmt, sql; mysql_date_and_time=true)` or `DBInterface.prepare(stmt, sql; mysql_date_and_time=true)`.
122
+
This will result in a column element type of `DateAndTime`, which is a simple struct of separate Date and Time parts, accessed like `dt.date` and `dt.time`.
123
+
"""
124
+
119
125
function Base.convert(::Type{DateTime}, mtime::MYSQL_TIME)
126
+
millis, micros =divrem(mtime.second_part, 1000)
120
127
if mtime.year ==0|| mtime.month ==0|| mtime.day ==0
y, code, pos = Parsers.typeparser(Int, buf, i +1, len, buf[1], Int16(0), Parsers.OPTIONS)
89
+
tm += Dates.Microsecond(y)
90
+
end
91
+
returnDateAndTime(dt, tm)
92
+
end
93
+
casterror(DateAndTime, ptr, len)
94
+
end
95
+
74
96
@noinlinewrongrow(i) =throw(ArgumentError("row $i is no longer valid; mysql results are forward-only iterators where each row is only valid when iterated"))
75
97
76
98
function Tables.getcolumn(r::TextRow, ::Type{T}, i::Int, nm::Symbol) where {T}
@@ -129,7 +151,7 @@ Specifying `mysql_store_result=false` will avoid buffering the full resultset to
129
151
the query, which has memory use advantages, though ties up the database server since resultset rows must be
130
152
fetched one at a time.
131
153
"""
132
-
function DBInterface.execute(conn::Connection, sql::AbstractString, params=(); mysql_store_result::Bool=true)
154
+
function DBInterface.execute(conn::Connection, sql::AbstractString, params=(); mysql_store_result::Bool=true, mysql_date_and_time::Bool=false)
133
155
checkconn(conn)
134
156
params != () &&error("`DBInterface.execute(conn, sql)` does not support parameter binding; see `?DBInterface.prepare(conn, sql)`")
135
157
clear!(conn)
@@ -154,7 +176,7 @@ function DBInterface.execute(conn::Connection, sql::AbstractString, params=(); m
154
176
nfields = API.numfields(result)
155
177
fields = API.fetchfields(result, nfields)
156
178
names = [ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Csize_t), x.name, x.name_length) for x in fields]
157
-
types = [juliatype(x.field_type, API.notnullable(x), API.isunsigned(x), API.isbinary(x)) for x in fields]
179
+
types = [juliatype(x.field_type, API.notnullable(x), API.isunsigned(x), API.isbinary(x), mysql_date_and_time) for x in fields]
158
180
elseif API.fieldcount(conn.mysql) ==0
159
181
rows_affected = API.affectedrows(conn.mysql)
160
182
names = Symbol[]
@@ -163,7 +185,7 @@ function DBInterface.execute(conn::Connection, sql::AbstractString, params=(); m
163
185
error("error with mysql resultset columns")
164
186
end
165
187
lookup =Dict(x => i for (i, x) inenumerate(names))
cursor.cursor.names = [ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Csize_t), x.name, x.name_length) for x in fields]
189
-
cursor.cursor.types = [juliatype(x.field_type, API.notnullable(x), API.isunsigned(x), API.isbinary(x)) for x in fields]
211
+
cursor.cursor.types = [juliatype(x.field_type, API.notnullable(x), API.isunsigned(x), API.isbinary(x), cursor.cursor.mysql_date_and_time) for x in fields]
0 commit comments