Skip to content

Commit 1b6ab33

Browse files
committed
Added nothing values handling (cf. PR #238):
- no difference for geometry columns. Both `nothing` and `missing` values map to an UNSET geometry field (null pointer) - field set to NULL for `missing` values and not set for `nothing` values
1 parent 541ff9b commit 1b6ab33

File tree

2 files changed

+47
-24
lines changed

2 files changed

+47
-24
lines changed

src/tables.jl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,20 @@ function _fromtable(
153153
rowgeoms = view(rowvalues, geomindices)
154154
rowfields = view(rowvalues, fieldindices)
155155
addfeature(layer) do feature
156-
# TODO: optimize once PR #238 is merged define in casse of `missing`
157-
# TODO: or `nothing` value, geom or field as to leave unset or set to null
156+
# For geometry fields both `missing` and `nothing` map to not geometry set
157+
# since in GDAL <= v"3.3.2", special fields as geometry field cannot be NULL
158+
# cf. `OGRFeature::IsFieldNull( int iField )` implemetation
158159
for (j, val) in enumerate(rowgeoms)
159160
val !== missing &&
160161
val !== nothing &&
161162
setgeom!(feature, j - 1, val)
162163
end
163164
for (j, val) in enumerate(rowfields)
164-
val !== missing &&
165-
val !== nothing &&
165+
if val === missing
166+
setfieldnull!(feature, j - 1)
167+
elseif val !== nothing
166168
setfield!(feature, j - 1, val)
169+
end
167170
end
168171
end
169172
end
@@ -178,15 +181,15 @@ function _fromtable(
178181
)::IFeatureLayer where {names}
179182
cols = Tables.columns(rows)
180183
types = (eltype(collect(col)) for col in cols)
181-
return _fromtable(Tables.Schema(names, types), rows; name=name)
184+
return _fromtable(Tables.Schema(names, types), rows; name = name)
182185
end
183186

184187
function _fromtable(::Nothing, rows; name::String = "")::IFeatureLayer
185188
state = iterate(rows)
186189
state === nothing && return IFeatureLayer()
187190
row, _ = state
188191
names = Tables.columnnames(row)
189-
return _fromtable(Tables.Schema(names, nothing), rows; name=name)
192+
return _fromtable(Tables.Schema(names, nothing), rows; name = name)
190193
end
191194

192195
"""
@@ -232,5 +235,5 @@ function IFeatureLayer(table; name::String = "")::IFeatureLayer
232235
# Extract table data
233236
rows = Tables.rows(table)
234237
schema = Tables.schema(table)
235-
return _fromtable(schema, rows; name=name)
238+
return _fromtable(schema, rows; name = name)
236239
end

test/test_tables.jl

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -807,33 +807,53 @@ using Tables
807807
return x
808808
end
809809
end
810-
function columntablevalues_toWKT(x)
810+
function ctv_toWKT(x)
811811
return Tuple(toWKT_withmissings.(x[i]) for i in 1:length(x))
812812
end
813+
"""
814+
nt2layer2nt_equals_nt(nt; force_no_schema=true)
815+
816+
Takes a NamedTuple, converts it to an IFeatureLayer and compares the NamedTuple
817+
to the one obtained from the IFeatureLayer conversion to table
818+
819+
_Notes:_
820+
1. _Table columns have geometry column first and then field columns as
821+
enforced by `Tables.columnnames`_
822+
2. _`nothing` values in geometry column are returned as `missing` from
823+
the NamedTuple roundtrip conversion, since geometry fields do not have the
824+
same distinction between NULL and UNSET values the fields have_
825+
826+
"""
813827
function nt2layer2nt_equals_nt(
814828
nt::NamedTuple;
815829
force_no_schema::Bool = false,
816830
)::Bool
817-
if force_no_schema
818-
(ct_in, ct_out) =
819-
Tables.columntable.((
820-
nt,
821-
AG._fromtable(nothing, Tables.rows(nt)),
822-
))
823-
else
824-
(ct_in, ct_out) =
825-
Tables.columntable.((nt, AG.IFeatureLayer(nt)))
826-
end
827-
(ctv_in, ctv_out) =
828-
columntablevalues_toWKT.(values.((ct_in, ct_out)))
831+
force_no_schema ?
832+
layer = AG._fromtable(nothing, Tables.rows(nt)) :
833+
layer = AG.IFeatureLayer(nt)
834+
ngeom = AG.ngeom(layer)
835+
(ct_in, ct_out) = Tables.columntable.((nt, layer))
836+
# we convert IGeometry values to WKT
837+
(ctv_in, ctv_out) = ctv_toWKT.(values.((ct_in, ct_out)))
838+
# we use two index functions to map ctv_in and ctv_out indices to the
839+
# sorted key list indices
829840
(spidx_in, spidx_out) =
830841
sortperm.(([keys(ct_in)...], [keys(ct_out)...]))
831842
return all([
832843
sort([keys(ct_in)...]) == sort([keys(ct_out)...]),
833844
all(
834845
all.([
835-
ctv_in[spidx_in[i]] .=== ctv_out[spidx_out[i]] for
836-
i in 1:length(ct_in)
846+
(
847+
# if we are comparing two geometry columns values, we
848+
# convert `nothing` values to `missing`, see note #2
849+
spidx_out[i] <= ngeom ?
850+
map(
851+
val ->
852+
(val === nothing || val === missing) ?
853+
missing : val,
854+
ctv_in[spidx_in[i]],
855+
) : ctv_in[spidx_in[i]]
856+
) .=== ctv_out[spidx_out[i]] for i in 1:length(nt)
837857
]),
838858
),
839859
])
@@ -919,8 +939,8 @@ using Tables
919939
]),
920940
],
921941
])
922-
@test_skip nt2layer2nt_equals_nt(nt; force_no_schema = true)
923-
@test_skip nt2layer2nt_equals_nt(nt)
942+
@test nt2layer2nt_equals_nt(nt; force_no_schema = true)
943+
@test nt2layer2nt_equals_nt(nt)
924944

925945
# Test with `missing` values
926946
nt = NamedTuple([

0 commit comments

Comments
 (0)