Skip to content

Commit 59a62c3

Browse files
committed
use model as domain object adding fields not in db table
1 parent 9efd170 commit 59a62c3

8 files changed

+83
-67
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Wasabi"
22
uuid = "cec542a4-22c5-46cb-8ca4-a46a7737b387"
33
authors = ["Mattia <[email protected]>"]
4-
version = "0.3.0"
4+
version = "0.4.0"
55

66
[deps]
77
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"

ext/WasabiPostgreSQLExt.jl

+4-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ function Wasabi.delete_schema(conn::LibPQ.Connection, m::Type{T}) where {T<:Wasa
2929
end
3030

3131
function Wasabi.create_schema(conn::LibPQ.Connection, m::Type{T}) where {T<:Wasabi.Model}
32-
columns = [(col, Wasabi.mapping(LibPQ.Connection, Wasabi.coltype(m, col))) for col in Wasabi.colnames(m)]
32+
autoincrement_fields = Wasabi.autoincrement_fields(m)
33+
columns = [(col, col in autoincrement_fields ? "SERIAL" : Wasabi.mapping(LibPQ.Connection, Wasabi.coltype(m, col))) for col in Wasabi.colnames(m) if !(col in Wasabi.exclude_fields(m))]
3334
query = "CREATE TABLE IF NOT EXISTS \"$(Wasabi.tablename(m))\" ($(join([String(col[1]) * " " * col[2] * (Wasabi.isnullable(m, col[1]) ? "" : " NOT NULL") for col in columns], ", "))"
3435

3536
constraints = Wasabi.constraints(m)
@@ -85,7 +86,7 @@ function Wasabi.first(conn::LibPQ.Connection, m::Type{T}, id) where {T<:Wasabi.M
8586
end
8687

8788
function Wasabi.insert!(conn::LibPQ.Connection, model::T) where {T<:Wasabi.Model}
88-
columns = filter(column -> column[2] !== nothing, Wasabi.model2tuple(model))
89+
columns = filter(column -> column[2] !== nothing && !(column[1] in Wasabi.exclude_fields(typeof(model))), Wasabi.model2tuple(model))
8990
fields = map(column -> column[1], columns)
9091
values = map(column -> Wasabi.to_sql_value(column[2]), columns)
9192

@@ -110,7 +111,7 @@ function Wasabi.delete!(conn::LibPQ.Connection, model::T) where {T<:Wasabi.Model
110111
end
111112

112113
function Wasabi.update!(conn::LibPQ.Connection, model::T) where {T<:Wasabi.Model}
113-
columns = filter(column -> column[2] !== nothing, Wasabi.model2tuple(model))
114+
columns = filter(column -> column[2] !== nothing && !(column[1] in Wasabi.exclude_fields(typeof(model))), Wasabi.model2tuple(model))
114115
fields = map(column -> column[1], columns)
115116
values = (map(column -> Wasabi.to_sql_value(column[2]), columns)..., Wasabi.to_sql_value(model.id))
116117

ext/WasabiSQLiteExt.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function Wasabi.delete_schema(db::SQLite.DB, m::Type{T}) where {T<:Wasabi.Model}
3131
end
3232

3333
function Wasabi.create_schema(db::SQLite.DB, m::Type{T}) where {T<:Wasabi.Model}
34-
columns = [(col, Wasabi.mapping(SQLite.DB, Wasabi.coltype(m, col))) for col in Wasabi.colnames(m)]
34+
columns = [(col, Wasabi.mapping(SQLite.DB, Wasabi.coltype(m, col))) for col in Wasabi.colnames(m) if !(col in Wasabi.exclude_fields(m))]
3535
query = "CREATE TABLE IF NOT EXISTS $(Wasabi.tablename(m)) ($(join([String(col[1]) * " " * col[2] * (Wasabi.isnullable(m, col[1]) ? "" : " NOT NULL") for col in columns], ", "))"
3636

3737
constraints = Wasabi.constraints(m)
@@ -87,7 +87,7 @@ function Wasabi.first(db::SQLite.DB, m::Type{T}, id) where {T<:Wasabi.Model}
8787
end
8888

8989
function Wasabi.insert!(db::SQLite.DB, model::T) where {T<:Wasabi.Model}
90-
columns = filter(column -> column[2] !== nothing, Wasabi.model2tuple(model))
90+
columns = filter(column -> column[2] !== nothing && !(column[1] in Wasabi.exclude_fields(typeof(model))), Wasabi.model2tuple(model))
9191
fields = map(column -> column[1], columns)
9292
values = map(column -> Wasabi.to_sql_value(column[2]), columns)
9393

@@ -114,7 +114,7 @@ function Wasabi.delete!(db::SQLite.DB, model::T) where {T<:Wasabi.Model}
114114
end
115115

116116
function Wasabi.update!(db::SQLite.DB, model::T) where {T<:Wasabi.Model}
117-
columns = filter(column -> column[2] !== nothing, Wasabi.model2tuple(model))
117+
columns = filter(column -> column[2] !== nothing && !(column[1] in Wasabi.exclude_fields(typeof(model))), Wasabi.model2tuple(model))
118118
fields = map(column -> column[1], columns)
119119
values = (map(column -> Wasabi.to_sql_value(column[2]), columns)..., Wasabi.to_sql_value(model.id))
120120

src/QueryBuilder.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ end
8787
function select(select::Union{Vector{Symbol},Nothing}=nothing)
8888
return function (q::Query)
8989
if select === nothing
90-
select = Wasabi.colnames(q.source)
90+
select = filter(col -> !(col in Wasabi.exclude_fields(q.source)), Wasabi.colnames(q.source))
9191
end
9292
q.select = vcat(q.select, [SelectExpr(source=q.source, field=col) for col in select])
9393
q

src/Wasabi.jl

+15-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export PostgreSQLConnectionConfiguration
1111
export Migrations
1212

1313
abstract type Model end
14+
abstract type Table end
1415
abstract type ModelConstraint end
1516
abstract type ConnectionConfiguration end
1617

@@ -75,12 +76,25 @@ end
7576
union_types(x::Union) = (x.a, union_types(x.b)...)
7677
union_types(x::Type) = (x,)
7778

79+
80+
"""
81+
exclude_fields(m::Type{T}) where {T <: Model}
82+
Returns the fields to exclude from the model when running queries.
83+
"""
84+
exclude_fields(m::Type{T}) where {T<:Wasabi.Model} = []
85+
86+
"""
87+
autoincrement_fields(m::Type{T}) where {T <: Model}
88+
Returns the fields that are autoincremented.
89+
"""
90+
autoincrement_fields(m::Type{T}) where {T<:Wasabi.Model} = [:id]
91+
7892
"""
7993
df2model(m::Type{T}, df::DataFrame) where {T <: Model}
8094
Converts the given DataFrame to the given model.
8195
"""
8296
function df2model(m::Type{T}, df::DataFrame) where {T<:Wasabi.Model}
83-
return [m(map(col -> row[col] !== missing ? Wasabi.from_sql_value(coltype(m, col), row[col]) : nothing, Wasabi.colnames(m))...) for row in eachrow(df)]
97+
return [m(map(col -> row[col] !== missing ? Wasabi.from_sql_value(coltype(m, col), row[col]) : nothing, filter(x -> String(x) in names(df), Wasabi.colnames(m)))...) for row in eachrow(df)]
8498
end
8599

86100
"""

test/postgresql.jl

+17-25
Original file line numberDiff line numberDiff line change
@@ -20,50 +20,45 @@
2020
@test Wasabi.mapping(LibPQ.Connection, Wasabi.AutoIncrement) == "SERIAL"
2121

2222
Mocking.activate()
23-
2423
patch = @patch LibPQ.execute(conn::LibPQ.Connection, query::String) = query
2524

2625
apply(patch) do
27-
@test Wasabi.delete_schema(conn, UserProfile) == "DROP TABLE IF EXISTS \"user_profile\""
2826
@test Wasabi.delete_schema(conn, User) == "DROP TABLE IF EXISTS \"user\""
27+
@test Wasabi.delete_schema(conn, Role) == "DROP TABLE IF EXISTS \"role\""
2928
@test Wasabi.create_schema(conn, User) == "CREATE TABLE IF NOT EXISTS \"user\" (id SERIAL NOT NULL, name TEXT NOT NULL, created_at TIMESTAMP NOT NULL, PRIMARY KEY (id))"
30-
end
31-
32-
apply(patch) do
33-
@test Wasabi.create_schema(conn, UserProfile) == "CREATE TABLE IF NOT EXISTS \"user_profile\" (id INTEGER NOT NULL, user_id INTEGER NOT NULL, bio TEXT, PRIMARY KEY (id), FOREIGN KEY (user_id) REFERENCES \"user\" (id), UNIQUE (user_id))"
29+
@test Wasabi.create_schema(conn, Role) == "CREATE TABLE IF NOT EXISTS \"role\" (id SERIAL NOT NULL, name TEXT NOT NULL, user_id INTEGER NOT NULL, PRIMARY KEY (id), FOREIGN KEY (user_id) REFERENCES \"user\" (id))"
3430
end
3531

3632
patch_execute_raw_query = @patch Wasabi.execute_query(conn::LibPQ.Connection, query::Wasabi.RawQuery, params::Vector{Any}=Any[]) = query
3733
apply(patch_execute_raw_query) do
3834
query = QueryBuilder.from(User) |> QueryBuilder.select([:id, :name]) |> QueryBuilder.limit(1) |> QueryBuilder.offset(1)
3935
@test Wasabi.execute_query(conn, query) == rq"SELECT user_alias.id, user_alias.name FROM \"user\" user_alias LIMIT 1 OFFSET 1"
4036

41-
query = QueryBuilder.from(User) |> QueryBuilder.select([:id, :name]) |> QueryBuilder.join(User, UserProfile, :inner, (:id, :user_id), [:bio]) |> QueryBuilder.join(UserProfile, UserPhone, :inner, (:id, :user_profile_id), [:phone])
42-
@test Wasabi.execute_query(conn, query) == rq"SELECT user_alias.id, user_alias.name, user_profile_alias.bio, user_phone_alias.phone FROM \"user\" user_alias INNER JOIN \"user_profile\" user_profile_alias ON user_alias.id = user_profile_alias.user_id INNER JOIN \"user_phone\" user_phone_alias ON user_profile_alias.id = user_phone_alias.user_profile_id"
37+
query = QueryBuilder.from(User) |> QueryBuilder.select([:id, :name]) |> QueryBuilder.join(User, Role, :inner, (:id, :user_id), [:name])
38+
@test Wasabi.execute_query(conn, query) == rq"SELECT user_alias.id, user_alias.name, role_alias.name FROM \"user\" user_alias INNER JOIN \"role\" role_alias ON user_alias.id = role_alias.user_id"
4339
end
4440

4541
Mocking.deactivate()
4642

47-
Wasabi.delete_schema(conn, UserProfile)
43+
Wasabi.delete_schema(conn, Role)
4844
Wasabi.delete_schema(conn, User)
4945

5046
Wasabi.create_schema(conn, User)
51-
Wasabi.create_schema(conn, UserProfile)
47+
Wasabi.create_schema(conn, Role)
5248

5349
dtnow = Dates.now()
5450
query = rq"INSERT INTO \"user\" (name, created_at) VALUES ($1, $2)"
5551
Wasabi.execute_query(conn, query, Any["John Doe", dtnow])
5652

5753
query = rq"SELECT * FROM \"user\""
5854
result = Wasabi.execute_query(conn, query)
59-
6055
@test length(result[!, :id]) == 1
6156
@test result[!, :id][1] == 1
6257
@test result[!, :name][1] == "John Doe"
6358
@test result[!, :created_at][1] == dtnow
6459

6560
user = Wasabi.df2model(User, result)[1]
66-
@test user.id == AutoIncrement(1)
61+
@test user.id == 1
6762
@test user.name == "John Doe"
6863
@test user.created_at == dtnow
6964

@@ -82,10 +77,10 @@
8277

8378
users = Wasabi.df2model(User, result)
8479
@test length(users) == 2
85-
@test users[1].id == AutoIncrement(1)
80+
@test users[1].id == 1
8681
@test users[1].name == "John Doe"
8782
@test users[1].created_at == dtnow
88-
@test users[2].id == AutoIncrement(2)
83+
@test users[2].id == 2
8984
@test users[2].name == "Jane Doe"
9085
@test users[2].created_at == dtnow
9186

@@ -107,29 +102,29 @@
107102
result = Wasabi.execute_query(conn, query)
108103
@test length(result[!, :id]) == 3
109104

110-
query == rq"SELECT user.id, user.name, user_profile.bio, user_phone.phone FROM \"user\" user INNER JOIN \"user_profile\" user_profile ON user.id = user_profile.user_id INNER JOIN \"user_phone\" user_phone ON user_profile.id = user_phone.user_profile_id"
111-
Wasabi.execute_query(conn, query)
112-
113105
user = Wasabi.first(conn, User, 1)
114-
@test user.id == AutoIncrement(1)
106+
@test user.id == 1
115107
@test user.name == "John Doe"
116108

117109
user = Wasabi.first(conn, User, 10)
118110
@test user === nothing
119111

120112
new_user = User("John Doe", dtnow)
121-
Wasabi.insert!(conn, new_user)
113+
keys = Wasabi.insert!(conn, new_user)
114+
@test keys[1, 1] == 5 # This is because even if we have only 3 users postgresql assign the id to the rollbacked user
122115

123116
user = Wasabi.first(conn, User, 5)
124-
@test user.id == AutoIncrement(5)
117+
@test user.id == 5
125118
@test user.name == "John Doe"
119+
@test user.created_at == dtnow
126120

127121
user.name = "Jane Doe"
128122
Wasabi.update!(conn, user)
129123

130124
user = Wasabi.first(conn, User, 5)
131-
@test user.id == AutoIncrement(5)
125+
@test user.id == 5
132126
@test user.name == "Jane Doe"
127+
@test user.created_at == dtnow
133128

134129
Wasabi.delete!(conn, user)
135130

@@ -143,7 +138,7 @@
143138
users = Wasabi.execute_query(conn, qb)
144139
@test length(users[!, :id]) == 1
145140
user = Wasabi.df2model(User, users)[1]
146-
@test user.id == AutoIncrement(2)
141+
@test user.id == 2
147142
@test user.name == "Jane Doe"
148143
@test user.created_at == dtnow
149144

@@ -163,8 +158,5 @@
163158
Wasabi.delete_all!(conn, User)
164159
@test length(Wasabi.all(conn, User)) == 0
165160

166-
Wasabi.delete_schema(conn, UserProfile)
167-
Wasabi.delete_schema(conn, User)
168-
169161
Wasabi.disconnect(conn)
170162
end

test/runtests.jl

+29-17
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,46 @@ using Mocking
88

99
Random.seed!(42)
1010

11+
mutable struct Role <: Wasabi.Model
12+
id::Union{Nothing, Int}
13+
name::String
14+
user_id::Int
15+
16+
Role(name::String, user_id::Int) = new(nothing, name, user_id)
17+
end
18+
1119
mutable struct User <: Wasabi.Model
12-
id::Union{Nothing, AutoIncrement}
20+
id::Union{Nothing, Int}
1321
name::String
1422
created_at::DateTime
23+
roles::Vector{Role}
1524

16-
User(name::String, created_at::DateTime) = new(nothing, name, created_at)
17-
User(id::AutoIncrement, name::String, created_at::DateTime) = new(id, name, created_at)
25+
User(name::String, created_at::DateTime, roles::Vector{Role} = Role[]) = new(nothing, name, created_at, roles)
26+
User(id::Number, name::String, created_at::DateTime, roles::Vector{Role} = Role[]) = new(id, name, created_at, roles)
1827
end
1928

20-
struct UserProfile <: Wasabi.Model
21-
id::Int
22-
user_id::Int
23-
bio::Union{String,Nothing}
24-
end
29+
# struct UserProfile <: Wasabi.Model
30+
# id::Int
31+
# user_id::Int
32+
# bio::Union{String,Nothing}
33+
# end
2534

26-
struct UserPhone <: Wasabi.Model
27-
id::Int
28-
user_profile_id::Int
29-
phone::String
30-
end
35+
# struct UserPhone <: Wasabi.Model
36+
# id::Int
37+
# user_profile_id::Int
38+
# phone::String
39+
# end
3140

3241
Wasabi.primary_key(m::Type{User}) = Wasabi.PrimaryKeyConstraint(Symbol[:id])
33-
Wasabi.primary_key(m::Type{UserProfile}) = Wasabi.PrimaryKeyConstraint(Symbol[:id])
42+
Wasabi.primary_key(m::Type{Role}) = Wasabi.PrimaryKeyConstraint(Symbol[:id])
43+
Wasabi.foreign_keys(m::Type{Role}) = [Wasabi.ForeignKeyConstraint(Symbol[:user_id], :user, Symbol[:id])]
44+
Wasabi.exclude_fields(m::Type{User}) = [:roles]
45+
46+
# Wasabi.primary_key(m::Type{UserProfile}) = Wasabi.PrimaryKeyConstraint(Symbol[:id])
3447

35-
Wasabi.foreign_keys(m::Type{UserProfile}) = [Wasabi.ForeignKeyConstraint(Symbol[:user_id], :user, Symbol[:id])]
36-
Wasabi.foreign_keys(m::Type{UserPhone}) = [Wasabi.ForeignKeyConstraint(Symbol[:user_profile_id], :user_profile, Symbol[:id])]
48+
# Wasabi.foreign_keys(m::Type{UserPhone}) = [Wasabi.ForeignKeyConstraint(Symbol[:user_profile_id], :user_profile, Symbol[:id])]
3749

38-
Wasabi.unique_constraints(m::Type{UserProfile}) = [Wasabi.UniqueConstraint(Symbol[:user_id])]
50+
# Wasabi.unique_constraints(m::Type{UserProfile}) = [Wasabi.UniqueConstraint(Symbol[:user_id])]
3951

4052
enabled_tests = lowercase.(ARGS)
4153
function addtests(fname)

test/sqlite.jl

+13-16
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,27 @@
1818

1919
apply(patch) do
2020
@test Wasabi.delete_schema(conn, User) == "DROP TABLE IF EXISTS user"
21-
@test Wasabi.delete_schema(conn, UserProfile) == "DROP TABLE IF EXISTS user_profile"
21+
@test Wasabi.delete_schema(conn, Role) == "DROP TABLE IF EXISTS role"
2222
@test Wasabi.create_schema(conn, User) == "CREATE TABLE IF NOT EXISTS user (id INTEGER NOT NULL, name TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (id))"
23-
end
24-
25-
apply(patch) do
26-
@test Wasabi.create_schema(conn, UserProfile) == "CREATE TABLE IF NOT EXISTS user_profile (id INTEGER NOT NULL, user_id INTEGER NOT NULL, bio TEXT, PRIMARY KEY (id), FOREIGN KEY (user_id) REFERENCES user (id), UNIQUE (user_id))"
23+
@test Wasabi.create_schema(conn, Role) == "CREATE TABLE IF NOT EXISTS role (id INTEGER NOT NULL, name TEXT NOT NULL, user_id INTEGER NOT NULL, PRIMARY KEY (id), FOREIGN KEY (user_id) REFERENCES user (id))"
2724
end
2825

2926
patch_execute_raw_query = @patch Wasabi.execute_query(db::SQLite.DB, query::Wasabi.RawQuery, params::Vector{Any}=Any[]) = query
3027
apply(patch_execute_raw_query) do
3128
query = QueryBuilder.from(User) |> QueryBuilder.select([:id, :name]) |> QueryBuilder.limit(1) |> QueryBuilder.offset(1)
3229
@test Wasabi.execute_query(conn, query) == rq"SELECT user_alias.id, user_alias.name FROM \"user\" user_alias LIMIT 1 OFFSET 1"
3330

34-
query = QueryBuilder.from(User) |> QueryBuilder.select([:id, :name]) |> QueryBuilder.join(User, UserProfile, :inner, (:id, :user_id), [:bio]) |> QueryBuilder.join(UserProfile, UserPhone, :inner, (:id, :user_profile_id), [:phone])
35-
@test Wasabi.execute_query(conn, query) == rq"SELECT user_alias.id, user_alias.name, user_profile_alias.bio, user_phone_alias.phone FROM \"user\" user_alias INNER JOIN \"user_profile\" user_profile_alias ON user_alias.id = user_profile_alias.user_id INNER JOIN \"user_phone\" user_phone_alias ON user_profile_alias.id = user_phone_alias.user_profile_id"
31+
query = QueryBuilder.from(User) |> QueryBuilder.select([:id, :name]) |> QueryBuilder.join(User, Role, :inner, (:id, :user_id), [:name])
32+
@test Wasabi.execute_query(conn, query) == rq"SELECT user_alias.id, user_alias.name, role_alias.name FROM \"user\" user_alias INNER JOIN \"role\" role_alias ON user_alias.id = role_alias.user_id"
3633
end
3734

3835
Mocking.deactivate()
3936

4037
Wasabi.delete_schema(conn, User)
41-
Wasabi.delete_schema(conn, UserProfile)
38+
Wasabi.delete_schema(conn, Role)
4239

4340
Wasabi.create_schema(conn, User)
44-
Wasabi.create_schema(conn, UserProfile)
41+
Wasabi.create_schema(conn, Role)
4542

4643
dtnow = Dates.now()
4744
query = rq"INSERT INTO user (name, created_at) VALUES (?, ?)"
@@ -55,7 +52,7 @@
5552
@test result[!, :created_at][1] == dtnow
5653

5754
user = Wasabi.df2model(User, result)[1]
58-
@test user.id == AutoIncrement(1)
55+
@test user.id == 1
5956
@test user.name == "John Doe"
6057
@test user.created_at == dtnow
6158

@@ -74,10 +71,10 @@
7471

7572
users = Wasabi.df2model(User, result)
7673
@test length(users) == 2
77-
@test users[1].id == AutoIncrement(1)
74+
@test users[1].id == 1
7875
@test users[1].name == "John Doe"
7976
@test users[1].created_at == dtnow
80-
@test users[2].id == AutoIncrement(2)
77+
@test users[2].id == 2
8178
@test users[2].name == "Jane Doe"
8279
@test users[2].created_at == dtnow
8380

@@ -100,7 +97,7 @@
10097
@test length(result[!, :id]) == 3
10198

10299
user = Wasabi.first(conn, User, 1)
103-
@test user.id == AutoIncrement(1)
100+
@test user.id == 1
104101
@test user.name == "John Doe"
105102

106103
user = Wasabi.first(conn, User, 10)
@@ -111,15 +108,15 @@
111108
@test keys[1, 1] == 4
112109

113110
user = Wasabi.first(conn, User, 4)
114-
@test user.id == AutoIncrement(4)
111+
@test user.id == 4
115112
@test user.name == "John Doe"
116113
@test user.created_at == dtnow
117114

118115
user.name = "Jane Doe"
119116
Wasabi.update!(conn, user)
120117

121118
user = Wasabi.first(conn, User, 4)
122-
@test user.id == AutoIncrement(4)
119+
@test user.id == 4
123120
@test user.name == "Jane Doe"
124121
@test user.created_at == dtnow
125122

@@ -135,7 +132,7 @@
135132
users = Wasabi.execute_query(conn, qb)
136133
@test length(users[!, :id]) == 1
137134
user = Wasabi.df2model(User, users)[1]
138-
@test user.id == AutoIncrement(2)
135+
@test user.id == 2
139136
@test user.name == "Jane Doe"
140137
@test user.created_at == dtnow
141138

0 commit comments

Comments
 (0)