Skip to content

Commit

Permalink
Merge pull request #29 from AlgebraicJulia/struct_acsets
Browse files Browse the repository at this point in the history
Adds StructACsets support
  • Loading branch information
bosonbaas authored Oct 26, 2021
2 parents 6a1c28e + a6c86a2 commit f539684
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 323 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
julia-version: ['1.0', '1.3', '1.6']
julia-version: ['1.6']
os: [ubuntu-latest]

steps:
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"

[compat]
AlgebraicPetri = "0.6"
AlgebraicPetri = "0.6, 0.7"
AutoHashEquals = "0.2.0"
Catlab = "0.11, 0.12"
Catlab = "0.13"
DataFrames = "0.21, 0.22, 1.0"
LibPQ = "1.4.0"
julia = "1.0"
Expand Down
1 change: 0 additions & 1 deletion src/AlgebraicRelations.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module AlgebraicRelations
include("functor.jl")
include("DB.jl")
include("Queries.jl")
include("Interface.jl")
Expand Down
37 changes: 22 additions & 15 deletions src/DB.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ module DB
using Catlab: @present
using Catlab.Present
using Catlab.CategoricalAlgebra.CSets
export TheorySQL, SchemaType, generate_schema_sql, @present, get_fields, TypeToSQL, typeToSQL
using Catlab.CSetDataStructures: struct_acset
export TheorySQL, generate_schema_sql, @present, get_fields, TypeToSQL, typeToSQL, @db_schema, AbstractSQL

TypeToSQL = Dict("String" => "text",
"Int" => "int",
Expand All @@ -12,36 +13,42 @@ module DB

typeToSQL(x) = TypeToSQL[string(x)]
@present TheorySQL(FreeSchema) begin
Int::Data
Int64::Data
Real::Data
String::Data
Bool::Data
Int::AttrType
Int64::AttrType
Real::AttrType
String::AttrType
Bool::AttrType
end;

function SchemaType(present::Presentation)
ACSetType(present){Int, Int64, Real, String, Bool}
end
@abstract_acset_type AbstractSQL

const AbstractSQL = AbstractACSetType(TheorySQL)
# TODO: This should be replacable with a cleaner method
macro db_schema(head)
struct_name = gensym()
quote
$(esc(:eval))(struct_acset($(Meta.quot(struct_name)), AbstractSQL, $(esc(head.args[2]))))
$(esc(head.args[1]))() = $(esc(struct_name)){$(esc(Int)), $(esc(Int)), $(esc(Real)), $(esc(String)), $(esc(Bool))}()
end
end

function generate_schema_sql(schema::AbstractACSet)
function generate_schema_sql(schema::AbstractSQL)
queries = map(collect(get_fields(schema))) do (name, col)
cols = ["$n $(typeToSQL(t))" for (n,t) in col]
"CREATE TABLE $name ($(join(cols, ", ")))"
end
string(join(queries, ";\n"), ";")
end

function get_fields(schema::AbstractACSet)
function get_fields(schema::AbstractSQL)
fields = Dict{Symbol, Array{Tuple{Symbol, Type},1}}()
for (name, table) in pairs(schema.tables)
for (name, table) in pairs(tables(schema))
table_name = name

# Get the column names and types
col_names, types = eltype(table).parameters
col_names = propertynames(table)
types = eltype.([schema[c] for c in col_names])
col_names = map(x -> Symbol(split(string(x), r"_\d+_")[end]), col_names)
fields[table_name] = map(zip(col_names,types.parameters)) do (n,t)
fields[table_name] = map(zip(col_names,types)) do (n,t)
(n, t)
end
end
Expand Down
82 changes: 39 additions & 43 deletions src/Presentations.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
module Presentations

using ..DB
import ..DB: @db_schema

using Catlab
using Catlab.Theories
using Catlab.Graphics
using Catlab.Present: Presentation
using Catlab.Theories.FreeSchema: Attr, Data
using Catlab.Theories.FreeSchema: Attr
using AlgebraicPetri
import Catlab.Theories: FreeSymmetricMonoidalCategory,
import Catlab.Programs: @program

export present_to_schema, @program, draw_workflow, FreeSymmetricMonoidalCategory,
add_types!, add_type!, add_process!, add_processes!, Presentation, ,
draw_schema
draw_schema, @db_schema, @present_to_schema

hasprop(o, p) = p in propertynames(o)

Expand All @@ -40,51 +41,46 @@ module Presentations
return map(hom->add_process!(p, hom), homs)
end

function present_to_schema(wf::Presentation)
gens = Array{GATExpr, 1}()
tables = Dict{Symbol, GATExpr}()
sym_app(s::Symbol, suffix::String) = Symbol(string(s, suffix))
get_syms(g::Union{GATExpr, Array}) = begin
if g isa Array
if eltype(g) == Symbol
return [g]
end
sym_array = Array{Array{Symbol, 1}, 1}()
for i in g
append!(sym_array, get_syms(i))
macro present_to_schema(head)
present_to_schema(head.args[1], head.args[2])
end

function present_to_schema(sch_name::Symbol, wf::Symbol)
quote
gens = Array{GATExpr, 1}()
tables = Dict{Symbol, GATExpr}()
sym_app(s::Symbol, suffix::String) = Symbol(string(s, suffix))
get_syms(g::Union{GATExpr, Array}) = begin
if g isa Array
if eltype(g) == Symbol
return [g]
end
sym_array = Array{Array{Symbol, 1}, 1}()
for i in g
append!(sym_array, get_syms(i))
end
return sym_array
elseif hasprop(g, :args)
return get_syms(g.args)
end
return sym_array
elseif hasprop(g, :args)
return get_syms(g.args)
end
end

# Evaluate objects to tables with attributes
#for g in generators(wf, :Ob)
# g_name = g.args[1]
# tab_name = sym_app(g_name, "_T")
# table = Ob(FreeSchema, tab_name)
# tables[g_name] = table
# push!(gens, table)
# push!(gens, Attr(sym_app(tab_name, "_1_id"), table, generator(TheorySQL, :Int64)))
# push!(gens, Attr(sym_app(tab_name, "_1_data"), table, generator(TheorySQL, g.args[2])))
#end

# Evaluate homs to purely data-connected tables
for g in generators(wf, :Hom)
g_name = g.args[1]
table = Ob(FreeSchema, g_name)
tables[g_name] = table
push!(gens, table)
append!(gens, map(enumerate(get_syms(g.type_args))) do (i, sym)
Attr(sym_app(g_name, "_$(i)_$(sym[1])$i"),
table, generator(TheorySQL, sym[2]))
end)
end
# Evaluate homs to purely data-connected tables
for g in generators($(esc(wf)), :Hom)
g_name = g.args[1]
table = Ob(FreeSchema, g_name)
tables[g_name] = table
push!(gens, table)
append!(gens, map(enumerate(get_syms(g.type_args))) do (i, sym)
Attr(sym_app(g_name, "_$(i)_$(sym[1])$i"),
table, generator(TheorySQL, sym[2]))
end)
end

@present p <: TheorySQL begin end
add_generators!(p, gens)
SchemaType(p)
@present p <: TheorySQL begin end
add_generators!(p, gens)
$(esc(:eval))(:(@db_schema $($(Meta.quot(sch_name)))($(p))))
end
end

function draw_schema(p::Presentation; kw...)
Expand Down
70 changes: 26 additions & 44 deletions src/Queries.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
module Queries
using Catlab: @present
import Catlab.Programs.RelationalPrograms: TheoryTypedRelationDiagram
import Catlab.Programs.RelationalPrograms: parse_relation_diagram
using Catlab.Programs.RelationalPrograms
using Catlab.Programs.RelationalPrograms: TheoryTypedRelationDiagram, TheoryTypedNamedRelationDiagram, TypedNamedRelationDiagram, parse_relation_diagram
using Catlab.Graphics
using Catlab.WiringDiagrams
using Catlab.CategoricalAlgebra.CSets
using ..DB
using ..Functors

# Used for the redefinition of copy_parts!
using Catlab.Theories: Schema, FreeSchema, dom, codom,
CatDesc, CatDescType, AttrDesc, AttrDescType, SchemaType,
ob_num, hom_num, data_num, attr_num, dom_num, codom_num

export TheoryQuery, Query, @query, to_sql, draw_query, to_prepared_sql, infer!

Expand All @@ -24,8 +17,7 @@ module Queries
:(!=) => ("<>", [:first, :second]),
)

@present TheoryQuery <: TheoryTypedRelationDiagram begin
field::Attr(Port, Name)
@present TheoryQuery <: TheoryTypedNamedRelationDiagram begin
Comparison::Ob
comp_port1::Hom(Comparison, Port)
comp_port2::Hom(Comparison, Port)
Expand All @@ -34,15 +26,13 @@ module Queries
# subpart(q, :port_type) == subpart(q, subpart(q, :junction), :junction_type)
end

const Query = ACSetType(TheoryQuery,
index=[:box, :junction, :outer_junction, :field],
unique_index=[:variable])
@acset_type Query(TheoryQuery,
index=[:box, :junction, :outer_junction, :port_name],
unique_index=[:variable])

NullableSym = Union{Symbol, Nothing}
Query() = Query{NullableSym, NullableSym, NullableSym}()



function infer!(wd, rels::Array{Tuple{Array{Symbol,1}, Array{Symbol,1}},1}; max_iter=2*length(rels))
# Perform multiple steps to fill in chains of relations
for i in 1:max_iter
Expand Down Expand Up @@ -101,43 +91,35 @@ module Queries
return all(is_defined), changed
end

function RelToQuery(schema)
function Query(schema, wd)
port_names = get_fields(schema)
function ob_to_sql(rel::UntypedRelationDiagram)
q = Query()
copy_parts!(q, rel)
name = subpart(rel, 1, :name)
q = Query()
copy_parts!(q, wd)

# Add types to each of the junctions by iterating through each box
for b in 1:nparts(q, :Box)
# Set junction and outer_port types (these will be inferred from schema types)
set_subpart!(q, :outer_port_type, nothing)
set_subpart!(q, :junction_type, nothing)
q[:outer_port_type] .= nothing
q[:outer_port_name] .= nothing
q[:junction_type] .= nothing
name = q[b, :name]

ports = incident(wd, b, :box)
# add comparison references for later type-inference
if name in keys(SQLOperators)
ports = incident(rel, 1, :box)
add_part!(q, :Comparison, comp_port1=ports[1], comp_port2=ports[2])
set_subparts!(q, 1:2, field=SQLOperators[name][2][1:2], port_type=[nothing, nothing])
set_subparts!(q, ports, port_name=SQLOperators[name][2][1:2], port_type=[nothing, nothing])
else
fields = [port_names[name][i][1] for i in 1:nparts(q, :Port)]
types = [Symbol(port_names[name][i][2]) for i in 1:nparts(q, :Port)]
set_subparts!(q, 1:nparts(q, :Port), field=fields, port_type=types)
fields = [port_names[name][i][1] for i in 1:length(ports)]
types = [Symbol(port_names[name][i][2]) for i in 1:length(ports)]
set_subparts!(q, ports, port_name=fields, port_type=types)
end
q
end

toQuery = Functor(ob_to_sql, Query)

function toSQL(rel::UntypedRelationDiagram)
q = toQuery(rel)
infer!(q, [([:port_type],[:junction, :junction_type]),
([:outer_junction, :junction_type],[:outer_port_type]),
([:comp_port1,:port_type],[:comp_port2,:port_type])]);
q
end
end

function Query(schema, wd)
RelToQuery(schema)(wd)
infer!(q, [([:port_type],[:junction, :junction_type]),
([:outer_junction, :junction_type],[:outer_port_type]),
([:comp_port1,:port_type],[:comp_port2,:port_type]),
([:outer_port_name],[:outer_junction, :variable])]);
q
end

macro query(schema, exprs...)
Expand Down Expand Up @@ -187,7 +169,7 @@ module Queries
op_inds = findall(x -> x in keys(SQLOperators), box_names)

outer_juncs = subpart(q, :outer_junction)
port_info = subparts(q, [:box, :junction, :field])
port_info = subparts(q, [:box, :junction, :port_name])
junctions = zeros(Int, nparts(q, :Junction))
variables = subpart(q, :variable)
prepared_junctions = findall(x -> string(x)[1] == '_', variables)
Expand Down Expand Up @@ -250,7 +232,7 @@ module Queries
end

function draw_query(q; kw...)
uwd = TypedRelationDiagram{NullableSym, NullableSym, NullableSym}()
uwd = TypedNamedRelationDiagram{NullableSym, NullableSym, NullableSym}()
copy_parts!(uwd, q)
to_graphviz(uwd; box_labels=:name, junction_labels=:variable, kw...)
end
Expand Down
Loading

0 comments on commit f539684

Please sign in to comment.