Skip to content
This repository has been archived by the owner on Oct 17, 2021. It is now read-only.

Commit

Permalink
Add initial foreign key actions
Browse files Browse the repository at this point in the history
  • Loading branch information
lucianolorenti committed Feb 29, 2020
1 parent ab895cf commit 3d59bda
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 33 deletions.
23 changes: 19 additions & 4 deletions src/Relational/Relational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,34 @@ function create_table_field(mapper::DBMapper, field::Field, table::Table, dbtype
end




on_event_action(t::OnEventAction) = throw("Not implemented")
on_event_action(t::Cascade) = "CASCADE"
on_event_action(t::DoNothing) = "NO ACTION"
on_event_action(t::SetNull) = "SET NULL"
on_event_action(t::Restrict) = "RESTRICT"

function create_reference_field(r::Relation)
return strip("FOREIGN KEY($(r.local_field)) " *
"REFERENCES $(r.referenced_table)($(r.referenced_field)) " *
"ON DELETE $(on_event_action(r.on_delete)) " *
"ON UPDATE $(on_event_action(r.on_update))")

end

function create_table_query(mapper::DBMapper, T::Type{<:Model}; if_not_exists::Bool=true) :: String
if mapper.dirty == true
analyze_relations(mapper)
end
table = mapper.tables[T]
create_table_fields = []
for field in table.fields
for field in fieldlist(table)
push!(create_table_fields,
create_table_field(mapper, field, table, mapper.pool.dbtype))
end

foreign_keys = ["FOREIGN KEY($(r.local_field)) REFERENCES $(r.referenced_table)($(r.referenced_field))"
for r in values(table.relations)]
foreign_keys = [create_reference_field(r) for r in values(table.relations)]
append!(create_table_fields, foreign_keys)

create_table_fields = join(create_table_fields, ", ")
Expand Down Expand Up @@ -149,7 +164,7 @@ function totuple(mapper::DBMapper, table::Table, dbtype::DataType, db_results) :
r = []
db_data = Dict(field=>getindex(row, field)
for field in propertynames(row))
for field in table.fields
for field in fieldlist(table)
push!(r, field.struct_field=>unmarshal(mapper, dbtype, field.type, db_data[field.name]))
end
push!(results, r)
Expand Down
56 changes: 39 additions & 17 deletions src/StructDatabaseMapping.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ module StructDatabaseMapping
export DBMapper, register!, create_table, DBId,
Nullable, analyze_relations, ForeignKey, select_one,
clean_table!, drop_table!, Model, getid, Foreign,
update!, exists
update!, exists, Cascade, SetNull, configure_relation,
Restrict

using Dates
using Requires
Expand Down Expand Up @@ -52,26 +53,42 @@ mutable struct Key
end
Key(f::Field; primary::Bool=false, auto_value::Bool=false) = Key([f], auto_value, primary)

abstract type OnEventAction end
struct Cascade <: OnEventAction end
struct Restrict <: OnEventAction end
struct SetNull <: OnEventAction end
struct SetDefault <: OnEventAction
value
end
struct Set <: OnEventAction
end
struct DoNothing <: OnEventAction end

struct Relation
mutable struct Relation
referenced_table::String
referenced_field::Symbol
local_field::Symbol
local_field::Symbol
on_delete::OnEventAction
on_update::OnEventAction
end
function Relation(referenced_table::String, referenced_field::Symbol, local_field::Symbol)
return Relation(referenced_table, referenced_field, local_field, DoNothing(), DoNothing())
end

mutable struct Table
name::String
data_type
fields::Array{Field}
fields::Dict{Symbol, Field}
relations::Dict{Field, Relation}
primary_key::Key
end
function idfield(t::Table) :: Symbol
return t.primary_key.field[1].name
end

isprimarykey(f::Field, t::Table) = t.primary_key.field[1] === f

function fieldlist(t::Table)
return sort(collect(values(t.fields)), by=x->x.name)
end

mutable struct DBMapper
tables::Dict{DataType, Table}
Expand Down Expand Up @@ -228,14 +245,14 @@ function register!(mapper::DBMapper, d::Type{T}; table_name::String="") where T
if table_name == ""
table_name = String(split(lowercase(string(T)), ".")[end])
end
fields = Field[]
fields = Dict{Symbol, Field}()
primary_key = nothing
for (field_name, field_type) in zip(fieldnames(d), fieldtypes(d))
db_field = Field(field_name, field_type)
if field_type <: DBId
primary_key = Key(db_field; primary=true, auto_value=element_type(db_field.type) <: Integer)
end
push!(fields, db_field)
fields[field_name] = db_field
end

table = Table(table_name,
Expand All @@ -255,7 +272,7 @@ After calling this function the mapper state is not dirty.
"""
function analyze_relations(mapper::DBMapper)
for table in values(mapper.tables)
for field in table.fields
for field in fieldlist(table)
if field.type <: ForeignKey
referenced_table = mapper.tables[element_type(field.type)]
referenced_field = referenced_table.primary_key.field[1]
Expand All @@ -275,7 +292,7 @@ Return the table field names for a given struct.
"""
function column_names(mapper::DBMapper, T::DataType) :: Array
table = mapper.tables[T]
return map(field->field.name, table.fields)
return map(field->field.name, fieldlist(table))
end

"""
Expand All @@ -286,7 +303,7 @@ Perhaps should be replaced directly with fieldnames
"""
function struct_fields(mapper::DBMapper, T::DataType) :: Array{Symbol}
table = mapper.tables[T]
return map(field->field.struct_field, table.fields)
return map(field->field.struct_field, fieldlist(table))
end


Expand All @@ -297,8 +314,8 @@ normalize(dbtype, x::AbstractDict) where T = JSON.json(x)
function struct_field_values(mapper, elem::T; ignore_primary_key::Bool=true, fields::Array{Symbol}=Symbol[]) where T
table = mapper.tables[T]
column_names = []
values = []
for field in table.fields
valuelist = []
for field in fieldlist(table)
if length(fields) > 0 && !(field.struct_field in fields)
continue
end
Expand All @@ -311,9 +328,9 @@ function struct_field_values(mapper, elem::T; ignore_primary_key::Bool=true, fie
continue
end
push!(column_names, field.name)
push!(values, normalize(mapper.pool.dbtype, getfield(elem, field.struct_field)))
push!(valuelist, normalize(mapper.pool.dbtype, getfield(elem, field.struct_field)))
end
return (column_names, values)
return (column_names, valuelist)
end


Expand Down Expand Up @@ -438,8 +455,13 @@ database_kind(c::Type{T}) where T = throw("Unknow database kind")



function configure_relation(mapper::DBMapper, T::Type, field; on_delete=true)
table = mapper.tables[T]
function configure_relation(mapper::DBMapper, T::Type, field_name::Symbol; on_delete::OnEventAction=DoNothing(), on_update::OnEventAction=DoNothing())
if mapper.dirty == true
analyze_relations(mapper)
end
table = mapper.tables[T]
table.relations[table.fields[field_name]].on_delete = on_delete
table.relations[table.fields[field_name]].on_update = on_update
end

"""
Expand Down
2 changes: 1 addition & 1 deletion test/includes/basic_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function _test_basic_functionalities(creator)

register!(mapper, Author)
register!(mapper, Book)

configure_relation(mapper, Book, :author, on_delete=Cascade())
@test haskey(mapper.tables, Author)
@test haskey(mapper.tables, Book)

Expand Down
12 changes: 6 additions & 6 deletions test/postgresql/TestLibPQ.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ function test_create_tables()

register!(mapper, Author)
register!(mapper, Book)

configure_relation(mapper, Book, :author, on_delete=Cascade())
@test (StructDatabaseMapping.create_table_query(mapper, Author)
== "CREATE TABLE IF NOT EXISTS author (" *
"id SERIAL PRIMARY KEY, " *
"name VARCHAR NOT NULL, " *
"age INTEGER NOT NULL, " *
"date TIMESTAMP NOT NULL)")
"date TIMESTAMP NOT NULL, " *
"id SERIAL PRIMARY KEY, " *
"name VARCHAR NOT NULL)")
@test (StructDatabaseMapping.create_table_query(mapper, Book)
== "CREATE TABLE IF NOT EXISTS book (" *
"id VARCHAR PRIMARY KEY, " *
"author_id INTEGER NOT NULL, " *
"data JSON NOT NULL, " *
"FOREIGN KEY(author_id) REFERENCES author(id))")
"id VARCHAR PRIMARY KEY, " *
"FOREIGN KEY(author_id) REFERENCES author(id) ON DELETE CASCADE ON UPDATE NO ACTION)")
end
function test()
test_create_tables()
Expand Down
17 changes: 12 additions & 5 deletions test/sqlite/TestSQLite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,26 @@ function test_create_tables()

register!(mapper, Author)
register!(mapper, Book)

configure_relation(mapper, Book, :author, on_delete=Cascade())
@test (StructDatabaseMapping.create_table_query(mapper, Author)
== "CREATE TABLE IF NOT EXISTS author (" *
"id INTEGER PRIMARY KEY, " *
"name VARCHAR NOT NULL, " *
"age INTEGER NOT NULL, " *
"date DATETIME NOT NULL)")
"date DATETIME NOT NULL, " *
"id INTEGER PRIMARY KEY, " *
"name VARCHAR NOT NULL)")
@test (StructDatabaseMapping.create_table_query(mapper, Book)
== "CREATE TABLE IF NOT EXISTS book (" *
"author_id INTEGER NOT NULL, " *
"data JSON NOT NULL, " *
"id VARCHAR PRIMARY KEY, " *
"FOREIGN KEY(author_id) REFERENCES author(id) ON DELETE CASCADE ON UPDATE NO ACTION)")
configure_relation(mapper, Book, :author, on_delete=Restrict(), on_update=Cascade())
@test (StructDatabaseMapping.create_table_query(mapper, Book)
== "CREATE TABLE IF NOT EXISTS book (" *
"author_id INTEGER NOT NULL, " *
"data JSON NOT NULL, " *
"FOREIGN KEY(author_id) REFERENCES author(id))")
"id VARCHAR PRIMARY KEY, " *
"FOREIGN KEY(author_id) REFERENCES author(id) ON DELETE RESTRICT ON UPDATE CASCADE)")
end
function test_sqlite()

Expand Down

0 comments on commit 3d59bda

Please sign in to comment.