From 4011c7ac02bea6d570453e00d0448b9ef1b25483 Mon Sep 17 00:00:00 2001 From: Kyrylo Simonov Date: Fri, 14 Jun 2024 21:40:48 -0500 Subject: [PATCH] Implement DataAPI metadata interface --- Project.toml | 2 + docs/src/test/other.md | 37 ++++++++++++ src/FunSQL.jl | 1 + src/catalogs.jl | 125 ++++++++++++++++++++++++++++++++--------- test/Project.toml | 1 + 5 files changed, 138 insertions(+), 28 deletions(-) diff --git a/Project.toml b/Project.toml index 0f3b1c15..df892bee 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.13.2" [deps] DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" +DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" @@ -13,6 +14,7 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" [compat] DBInterface = "2.5" +DataAPI = "1.13" LRUCache = "1.3" OrderedCollections = "1.4" PrettyPrinting = "0.3.2, 0.4" diff --git a/docs/src/test/other.md b/docs/src/test/other.md index 88c7f45b..c1bf6611 100644 --- a/docs/src/test/other.md +++ b/docs/src/test/other.md @@ -133,6 +133,9 @@ A `SQLTable` object behaves like a read-only dictionary. person["person_id"] #-> SQLColumn(:person_id) + person[1] + #-> SQLColumn(:person_id) + person[:visit_occurrence] #-> ERROR: KeyError: key :visit_occurrence not found @@ -263,6 +266,40 @@ Catalog objects can be assigned arbitrary metadata. metadata = [:model => "OMOP"]) =# +FunSQL metadata supports DataAPI metadata interface. + + using DataAPI + + DataAPI.metadata(metadata_catalog) + #-> Dict("model" => "OMOP") + + DataAPI.metadata(metadata_catalog, style = true) + #-> Dict("model" => ("OMOP", :default)) + + DataAPI.metadata(metadata_catalog, :name, :default) + #-> :default + + DataAPI.metadata(metadata_catalog[:person])["caption"] + #-> "Person" + + DataAPI.metadata(metadata_catalog[:person], :is_view, true) + #-> false + + DataAPI.colmetadata(metadata_catalog[:person])[:person_id]["label"] + #-> "Person ID" + + DataAPI.colmetadata(metadata_catalog[:person], 1, :label) + #-> "Person ID" + + DataAPI.colmetadata(metadata_catalog[:person], :year_of_birth, :label, "") + #-> "" + + DataAPI.metadata(metadata_catalog[:person][:person_id]) + #-> Dict("label" => "Person ID") + + DataAPI.metadata(metadata_catalog[:person][:person_id], :label, "") + #-> "Person ID" + ## `SQLDialect` diff --git a/src/FunSQL.jl b/src/FunSQL.jl index ff77d7e3..09537994 100644 --- a/src/FunSQL.jl +++ b/src/FunSQL.jl @@ -84,6 +84,7 @@ using OrderedCollections: OrderedDict, OrderedSet using Tables using DBInterface using LRUCache +using DataAPI const SQLLiteralType = Union{Missing, Bool, Number, AbstractString, Dates.AbstractTime} diff --git a/src/catalogs.jl b/src/catalogs.jl index 890fe28e..a59992f6 100644 --- a/src/catalogs.jl +++ b/src/catalogs.jl @@ -14,6 +14,22 @@ _metadata(dict::SQLMetadata, kvs...) = _metadata(other) = _metadata(SQLMetadata(), pairs(other)...) +_metadata_style(@nospecialize(val)) = + :default + +_metadata_keys(dict::SQLMetadata) = + Base.Generator(string, keys(dict)) + +_metadata_get(dict::SQLMetadata, key::Union{Symbol, AbstractString}; style::Bool) = + let val = dict[Symbol(key)] + style ? (val, _metadata_style(val)) : val + end + +_metadata_get(dict::SQLMetadata, key::Union{Symbol, AbstractString}, default; style::Bool) = + let val = get(dict, Symbol(key), default) + style ? (val, _metadata_style(val)) : val + end + """ SQLColumn(; name, metadata = nothing) SQLColumn(name; metadata = nothing) @@ -46,26 +62,17 @@ function PrettyPrinting.quoteof(col::SQLColumn; limit::Bool = false) ex end -_column_map(columns::OrderedDict{Symbol, SQLColumn}) = - columns - -_column_map(columns::AbstractVector{Pair{Symbol, SQLColumn}}) = - OrderedDict{Symbol, SQLColumn}(columns) - -_column_map(columns) = - OrderedDict{Symbol, SQLColumn}(Pair{Symbol, SQLColumn}[_column_entry(c) for c in columns]) - -_column_entry(c::Symbol) = - c => SQLColumn(c) +DataAPI.metadatasupport(::Type{SQLColumn}) = + (read = true, write = false) -_column_entry(c::AbstractString) = - _column_entry(Symbol(c)) +DataAPI.metadata(col::SQLColumn, key::Union{Symbol, AbstractString}; style::Bool = false) = + _metadata_get(col.metadata, key; style) -_column_entry(c::SQLColumn) = - c.name => c +DataAPI.metadata(col::SQLColumn, key::Union{Symbol, AbstractString}, default; style::Bool = false) = + _metadata_get(col.metadata, key, default; style) -_column_entry((n, c)::Pair{<:Union{Symbol, AbstractString}, SQLColumn}) = - Symbol(n) => c +DataAPI.metadatakeys(col::SQLColumn) = + _metadata_keys(col.metadata) """ SQLTable(; qualifiers = [], name, columns, metadata = nothing) @@ -120,6 +127,27 @@ SQLTable(name; qualifiers = Symbol[], columns, metadata = nothing) = SQLTable(name, columns...; qualifiers = Symbol[], metadata = nothing) = SQLTable(qualifiers = qualifiers, name = name, columns = [columns...], metadata = metadata) +_column_map(columns::OrderedDict{Symbol, SQLColumn}) = + columns + +_column_map(columns::AbstractVector{Pair{Symbol, SQLColumn}}) = + OrderedDict{Symbol, SQLColumn}(columns) + +_column_map(columns) = + OrderedDict{Symbol, SQLColumn}(Pair{Symbol, SQLColumn}[_column_entry(c) for c in columns]) + +_column_entry(c::Symbol) = + c => SQLColumn(c) + +_column_entry(c::AbstractString) = + _column_entry(Symbol(c)) + +_column_entry(c::SQLColumn) = + c.name => c + +_column_entry((n, c)::Pair{<:Union{Symbol, AbstractString}, SQLColumn}) = + Symbol(n) => c + Base.show(io::IO, tbl::SQLTable) = print(io, quoteof(tbl, limit = true)) @@ -158,28 +186,43 @@ Base.get(default::Base.Callable, tbl::SQLTable, key::Union{Symbol, AbstractStrin Base.getindex(tbl::SQLTable, key::Union{Symbol, AbstractString}) = tbl.columns[Symbol(key)] +Base.getindex(tbl::SQLTable, key::Integer) = + tbl.columns.vals[key] + Base.iterate(tbl::SQLTable, state...) = iterate(tbl.columns, state...) Base.length(tbl::SQLTable) = length(tbl.columns) -const default_cache_maxsize = 256 +DataAPI.metadatasupport(::Type{SQLTable}) = + (read = true, write = false) -_table_map(tables::Dict{Symbol, SQLTable}) = - tables +DataAPI.metadata(tbl::SQLTable, key::Union{Symbol, AbstractString}; style::Bool = false) = + _metadata_get(tbl.metadata, key; style) -_table_map(tables::AbstractVector{Pair{Symbol, SQLTable}}) = - Dict{Symbol, SQLTable}(tables) +DataAPI.metadata(tbl::SQLTable, key::Union{Symbol, AbstractString}, default; style::Bool = false) = + _metadata_get(tbl.metadata, key, default; style) -_table_map(tables) = - Dict{Symbol, SQLTable}(Pair{Symbol, SQLTable}[_table_entry(t) for t in tables]) +DataAPI.metadatakeys(tbl::SQLTable) = + _metadata_keys(tbl.metadata) -_table_entry(t::SQLTable) = - t.name => t +DataAPI.colmetadatasupport(::Type{SQLTable}) = + (read = true, write = false) -_table_entry((n, t)::Pair{<:Union{Symbol, AbstractString}, SQLTable}) = - Symbol(n) => t +DataAPI.colmetadata(tbl::SQLTable, col::Union{Symbol, Integer}, key::Union{Symbol, AbstractString}; style::Bool = false) = + _metadata_get(tbl[col].metadata, key; style) + +DataAPI.colmetadata(tbl::SQLTable, col::Union{Symbol, Integer}, key::Union{Symbol, AbstractString}, default; style::Bool = false) = + _metadata_get(tbl[col].metadata, key, default; style) + +DataAPI.colmetadatakeys(tbl::SQLTable) = + (k => _metadata_keys(v.metadata) for (k, v) in tbl.columns) + +DataAPI.colmetadatakeys(tbl::SQLTable, col::Union{Symbol, Integer}) = + _metadata_keys(tbl[col].metadata) + +const default_cache_maxsize = 256 """ SQLCatalog(; tables = Dict{Symbol, SQLTable}(), @@ -239,6 +282,21 @@ end SQLCatalog(tables...; dialect = :default, cache = default_cache_maxsize, metadata = nothing) = SQLCatalog(tables = tables, dialect = dialect, cache = cache, metadata = metadata) +_table_map(tables::Dict{Symbol, SQLTable}) = + tables + +_table_map(tables::AbstractVector{Pair{Symbol, SQLTable}}) = + Dict{Symbol, SQLTable}(tables) + +_table_map(tables) = + Dict{Symbol, SQLTable}(Pair{Symbol, SQLTable}[_table_entry(t) for t in tables]) + +_table_entry(t::SQLTable) = + t.name => t + +_table_entry((n, t)::Pair{<:Union{Symbol, AbstractString}, SQLTable}) = + Symbol(n) => t + function PrettyPrinting.quoteof(c::SQLCatalog) ex = Expr(:call, nameof(SQLCatalog)) for name in sort!(collect(keys(c.tables))) @@ -310,3 +368,14 @@ Base.iterate(c::SQLCatalog, state...) = Base.length(c::SQLCatalog) = length(c.tables) +DataAPI.metadatasupport(::Type{SQLCatalog}) = + (read = true, write = false) + +DataAPI.metadata(c::SQLCatalog, key::Union{Symbol, AbstractString}; style::Bool = false) = + _metadata_get(c.metadata, key; style) + +DataAPI.metadata(c::SQLCatalog, key::Union{Symbol, AbstractString}, default; style::Bool = false) = + _metadata_get(c.metadata, key, default; style) + +DataAPI.metadatakeys(c::SQLCatalog) = + _metadata_keys(c.metadata) diff --git a/test/Project.toml b/test/Project.toml index e91d85c3..d8de25a1 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] +DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"