@@ -101,7 +101,6 @@ is not in UTF-8.
101
101
bytes_view (pqv:: PQValue ) = unsafe_wrap (Vector{UInt8}, data_pointer (pqv), num_bytes (pqv) + 1 )
102
102
103
103
Base. String (pqv:: PQValue ) = unsafe_string (pqv)
104
- Base. parse (:: Type{String} , pqv:: PQValue ) = unsafe_string (pqv)
105
104
Base. convert (:: Type{String} , pqv:: PQValue ) = String (pqv)
106
105
Base. length (pqv:: PQValue ) = length (string_view (pqv))
107
106
Base. lastindex (pqv:: PQValue ) = lastindex (string_view (pqv))
@@ -115,23 +114,25 @@ Base.lastindex(pqv::PQValue) = lastindex(string_view(pqv))
115
114
Parse a value of type `T` from a `PQValue`.
116
115
By default, this uses any existing `parse` method for parsing a value of type `T` from a
117
116
`String`.
117
+
118
+ You can implement default PostgreSQL-specific parsing for a given type by overriding
119
+ `pqparse`.
118
120
"""
119
- Base. parse (:: Type{T} , pqv:: PQValue ) where {T} = parse (T, string_view (pqv))
121
+ Base. parse (:: Type{T} , pqv:: PQValue ) where {T} = pqparse (T, string_view (pqv))
120
122
121
- # allow parsing as a Symbol anything which works as a String
122
- Base . parse (:: Type{Symbol } , pqv :: PQValue ) = Symbol ( string_view (pqv))
123
+ """
124
+ LibPQ.pqparse (::Type{T }, str::AbstractString) -> T
123
125
124
- function Base. iterate (pqv:: PQValue )
125
- sv = string_view (pqv)
126
- iterate (pqv, (sv, ()))
127
- end
128
- function Base. iterate (pqv:: PQValue , state)
129
- sv, i = state
130
- iter = iterate (sv, i... )
131
- iter === nothing && return nothing
132
- c, new_sv_state = iter
133
- return (c, (sv, (new_sv_state,)))
134
- end
126
+ Parse a value of type `T` from any `AbstractString`.
127
+ This is used to parse PostgreSQL's output format.
128
+ """
129
+ function pqparse end
130
+
131
+ # Fallback method
132
+ pqparse (:: Type{T} , str:: AbstractString ) where {T} = parse (T, str)
133
+
134
+ # allow parsing as a Symbol anything which works as a String
135
+ pqparse (:: Type{Symbol} , str:: AbstractString ) = Symbol (str)
135
136
136
137
# # integers
137
138
_DEFAULT_TYPE_MAP[:int2 ] = Int16
@@ -152,21 +153,26 @@ _DEFAULT_TYPE_MAP[:numeric] = Decimal
152
153
153
154
# # character
154
155
# bpchar is char(n)
155
- function Base . parse (:: Type{String} , pqv :: PQValue{PQ_SYSTEM_TYPES[:bpchar]} )
156
- return String (rstrip (string_view (pqv) , ' ' ))
156
+ function pqparse (:: Type{String} , str :: AbstractString )
157
+ return String (rstrip (str , ' ' ))
157
158
end
158
159
# char is "char"
159
160
_DEFAULT_TYPE_MAP[:char ] = PQChar
160
- Base . parse (:: Type{PQChar} , pqv :: PQValue{PQ_SYSTEM_TYPES[:char]} ) = PQChar (first (pqv ))
161
- Base . parse (:: Type{Char} , pqv :: PQValue{PQ_SYSTEM_TYPES[:char]} ) = Char (parse (PQChar, pqv ))
161
+ pqparse (:: Type{PQChar} , str :: AbstractString ) = PQChar (first (str ))
162
+ pqparse (:: Type{Char} , str :: AbstractString ) = Char (pqparse (PQChar, str ))
162
163
# varchar, text, and name are all String
163
164
164
165
# # binary data
165
166
166
167
_DEFAULT_TYPE_MAP[:bytea ] = Vector{UInt8}
168
+
169
+ # Needs it's own `parse` method as it uses bytes_view instead of string_view
167
170
function Base. parse (:: Type{Vector{UInt8}} , pqv:: PQValue{PQ_SYSTEM_TYPES[:bytea]} )
171
+ pqparse (Vector{UInt8}, bytes_view (pqv))
172
+ end
173
+
174
+ function pqparse (:: Type{Vector{UInt8}} , bytes:: Array{UInt8,1} )
168
175
byte_length = Ref {Csize_t} (0 )
169
- bytes = bytes_view (pqv)
170
176
171
177
unescaped_ptr = libpq_c. PQunescapeBytea (bytes, byte_length)
172
178
186
192
_DEFAULT_TYPE_MAP[:bool ] = Bool
187
193
const BOOL_TRUE = r" ^\s *(t|true|y|yes|on|1)\s *$" i
188
194
const BOOL_FALSE = r" ^\s *(f|false|n|no|off|0)\s *$" i
189
- function Base. parse (:: Type{Bool} , pqv:: PQValue{PQ_SYSTEM_TYPES[:bool]} )
190
- str = string_view (pqv)
191
-
195
+ function pqparse (:: Type{Bool} , str:: AbstractString )
192
196
if occursin (BOOL_TRUE, str)
193
197
return true
194
198
elseif occursin (BOOL_FALSE, str)
@@ -208,9 +212,7 @@ _trunc_seconds(str) = replace(str, r"(\.[\d]{3})\d+" => s"\g<1>")
208
212
209
213
_DEFAULT_TYPE_MAP[:timestamp ] = DateTime
210
214
const TIMESTAMP_FORMAT = dateformat " y-m-d HH:MM:SS.s" # .s is optional here
211
- function Base. parse (:: Type{DateTime} , pqv:: PQValue{PQ_SYSTEM_TYPES[:timestamp]} )
212
- str = string_view (pqv)
213
-
215
+ function pqparse (:: Type{DateTime} , str:: AbstractString )
214
216
if str == " infinity"
215
217
return typemax (DateTime)
216
218
elseif str == " -infinity"
@@ -231,9 +233,7 @@ const TIMESTAMPTZ_FORMATS = (
231
233
dateformat " y-m-d HH:MM:SS.ssz" ,
232
234
dateformat " y-m-d HH:MM:SS.sssz" ,
233
235
)
234
- function Base. parse (:: Type{ZonedDateTime} , pqv:: PQValue{PQ_SYSTEM_TYPES[:timestamptz]} )
235
- str = string_view (pqv)
236
-
236
+ function pqparse (:: Type{ZonedDateTime} , str:: AbstractString )
237
237
if str == " infinity"
238
238
return ZonedDateTime (typemax (DateTime), tz " UTC" )
239
239
elseif str == " -infinity"
@@ -249,9 +249,7 @@ function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:timesta
249
249
end
250
250
251
251
_DEFAULT_TYPE_MAP[:date ] = Date
252
- function Base. parse (:: Type{Date} , pqv:: PQValue{PQ_SYSTEM_TYPES[:date]} )
253
- str = string_view (pqv)
254
-
252
+ function pqparse (:: Type{Date} , str:: AbstractString )
255
253
if str == " infinity"
256
254
return typemax (Date)
257
255
elseif str == " -infinity"
@@ -262,9 +260,7 @@ function Base.parse(::Type{Date}, pqv::PQValue{PQ_SYSTEM_TYPES[:date]})
262
260
end
263
261
264
262
_DEFAULT_TYPE_MAP[:time ] = Time
265
- function Base. parse (:: Type{Time} , pqv:: PQValue{PQ_SYSTEM_TYPES[:time]} )
266
- str = string_view (pqv)
267
-
263
+ function pqparse (:: Type{Time} , str:: AbstractString )
268
264
try
269
265
return parse (Time, str)
270
266
catch err
@@ -316,12 +312,12 @@ end
316
312
317
313
# parse the iso_8601 interval output format
318
314
# https://www.postgresql.org/docs/10/datatype-datetime.html#DATATYPE-INTERVAL-OUTPUT
319
- function Base . parse (:: Type{Dates.CompoundPeriod} , pqv :: PQValue{PQ_SYSTEM_TYPES[:interval]} )
315
+ function pqparse (:: Type{Dates.CompoundPeriod} , str :: AbstractString )
320
316
interval_regex = INTERVAL_REGEX[]
321
- matched = match (interval_regex, string_view (pqv) )
317
+ matched = match (interval_regex, str )
322
318
323
319
if matched === nothing
324
- error (" Couldn't parse $( string_view (pqv)) as interval using regex $interval_regex " )
320
+ error (" Couldn't parse $str as interval using regex $interval_regex " )
325
321
end
326
322
327
323
periods = Period[]
@@ -364,6 +360,43 @@ function Base.parse(::Type{Dates.CompoundPeriod}, pqv::PQValue{PQ_SYSTEM_TYPES[:
364
360
return Dates. CompoundPeriod (periods)
365
361
end
366
362
363
+ # # ranges
364
+ _DEFAULT_TYPE_MAP[:int4range ] = Interval{Int32}
365
+ _DEFAULT_TYPE_MAP[:int8range ] = Interval{Int64}
366
+ _DEFAULT_TYPE_MAP[:numrange ] = Interval{Decimal}
367
+ _DEFAULT_TYPE_MAP[:tsrange ] = Interval{DateTime}
368
+ _DEFAULT_TYPE_MAP[:tstzrange ] = Interval{ZonedDateTime}
369
+ _DEFAULT_TYPE_MAP[:daterange ] = Interval{Date}
370
+
371
+ # Matches anything but the start or end of an interval or a comma
372
+ const RANGE_ITEM = " [^\\ [\\ (\\ ]\\ ),]+"
373
+ # Makes sure the string starts and ends with a bracket or parentheses, has two items in the
374
+ # interval, and a comma to separate the two items.
375
+ const RANGE_REGEX = Regex (" ^([\\ [\\ (])($RANGE_ITEM ),($RANGE_ITEM )([\\ ]\\ )])\$ " )
376
+ get_inclusivity (ch) = ch in (" [" , " ]" ) ? true : false
377
+
378
+ function pqparse (:: Type{Interval{T}} , str:: AbstractString ) where {T}
379
+ if str == " empty"
380
+ return Interval {T} ()
381
+ end
382
+
383
+ matched = match (RANGE_REGEX, str)
384
+ if matched === nothing
385
+ error (" Couldn't parse $str as range using regex $RANGE_REGEX " )
386
+ else
387
+ matched = matched. captures
388
+ end
389
+
390
+ start_inclusivity = get_inclusivity (matched[1 ])
391
+ end_inclusivity = get_inclusivity (matched[4 ])
392
+
393
+ # Datetime formats have quotes around them so we strip those out
394
+ start = pqparse (T, strip (matched[2 ], [' "' ]))
395
+ endpoint = pqparse (T, strip (matched[3 ], [' "' ]))
396
+
397
+ return Interval (start, endpoint, start_inclusivity, end_inclusivity)
398
+ end
399
+
367
400
# # arrays
368
401
# numeric arrays never have double quotes and always use ',' as a separator
369
402
parse_numeric_element (:: Type{T} , str) where T = parse (T, str)
@@ -443,10 +476,10 @@ for pq_eltype in ("int2", "int4", "int8", "float4", "float8", "oid", "numeric")
443
476
_DEFAULT_TYPE_MAP[array_oid] = AbstractArray{jl_missingtype}
444
477
445
478
for jl_eltype in (jl_type, jl_missingtype)
446
- @eval function Base . parse (
447
- :: Type{A} , pqv :: PQValue{$array_oid}
479
+ @eval function pqparse (
480
+ :: Type{A} , str :: AbstractString
448
481
) where A <: AbstractArray{$jl_eltype}
449
- parse_numeric_array ($ jl_eltype, string_view (pqv) ):: A
482
+ parse_numeric_array ($ jl_eltype, str ):: A
450
483
end
451
484
end
452
485
end
0 commit comments