@@ -10,7 +10,7 @@ export DB, Node, Edge
10
10
# -----------------------------------------------------------------------------# utils
11
11
function single_result_execute (db, stmt, args... )
12
12
ex = execute (db, stmt, args... )
13
- isempty (ex) ? nothing : values (first (ex))[ 1 ]
13
+ isempty (ex) ? nothing : first (first (ex))
14
14
end
15
15
16
16
function print_props (io:: IO , o:: Config )
22
22
23
23
24
24
# -----------------------------------------------------------------------------# Model
25
- # Nodes describe entities (discrete objects) of a domain.
26
- # Nodes can have zero or more labels to define (classify) what kind of nodes they are.
27
- # Nodes and relationships can have properties (key-value pairs), which further describe them.
28
-
29
- # Relationships describes a connection between a source node and a target node.
30
- # Relationships always has a direction (one direction).
31
- # Relationships must have a type (one type) to define (classify) what type of relationship they are.
32
-
33
- # Nouns-nodes, Adjectives-properties, Verbs-relationship, Adverbs-properties on relationship
34
-
35
- # Property Graph Model on page 4
36
- # https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf
37
-
38
25
struct Node
39
26
id:: Int
40
27
labels:: Vector{String}
@@ -55,6 +42,7 @@ function Base.show(io::IO, o::Node)
55
42
end
56
43
end
57
44
args (n:: Node ) = (n. id, join (n. labels, ' ;' ), JSON3. write (n. props))
45
+ Base.:(== )(a:: Node , b:: Node ) = all (getfield (a,f) == getfield (b,f) for f in fieldnames (Node))
58
46
59
47
60
48
struct Edge
@@ -77,7 +65,9 @@ function Base.show(io::IO, o::Edge)
77
65
printstyled (io, join (keys (o. props), " , " ), color= :light_green )
78
66
end
79
67
end
80
- args (e:: Edge ) = (e. source, e. target, join (e. type, ' ;' ), JSON3. write (e. props))
68
+ args (e:: Edge ) = (e. source, e. target, e. type, JSON3. write (e. props))
69
+ Base.:(== )(a:: Edge , b:: Edge ) = all (getfield (a,f) == getfield (b,f) for f in fieldnames (Edge))
70
+
81
71
82
72
# -----------------------------------------------------------------------------# DB
83
73
struct DB
@@ -103,11 +93,13 @@ struct DB
103
93
type TEXT NOT NULL,
104
94
props TEXT,
105
95
FOREIGN KEY(source) REFERENCES nodes(id),
106
- FOREIGN KEY(target) REFERENCES nodes(id)
96
+ FOREIGN KEY(target) REFERENCES nodes(id),
97
+ UNIQUE(source, target, type)
107
98
);" ,
108
99
" CREATE INDEX IF NOT EXISTS source_idx ON edges(source);" ,
109
100
" CREATE INDEX IF NOT EXISTS target_idx ON edges(target);" ,
110
101
" CREATE INDEX IF NOT EXISTS type_idx ON edges(type);" ,
102
+ " CREATE INDEX IF NOT EXISTS source_target_type_idx ON edges(source, target, type);"
111
103
])
112
104
new (db)
113
105
end
@@ -125,92 +117,68 @@ Base.length(db::DB) = n_nodes(db)
125
117
Base. size (db:: DB ) = (n_nodes= n_nodes (db), n_edges= n_edges (db))
126
118
127
119
# -----------------------------------------------------------------------------# nodes
120
+ function Base. push! (db:: DB , node:: Node ; upsert= false )
121
+ upsert ?
122
+ execute (db, " INSERT INTO nodes VALUES(?, ?, json(?)) ON CONFLICT(id) DO UPDATE SET labels=excluded.labels, props=excluded.props" , args (node)) :
123
+ execute (db, " INSERT INTO nodes VALUES(?, ?, json(?))" , args (node))
124
+ db
125
+ end
126
+
128
127
function Base. getindex (db:: DB , id:: Integer )
129
128
res = execute (db, " SELECT * FROM nodes WHERE id = ?" , (id,))
130
- isempty (res) ? throw ( BoundsError (db, id) ) : Node (first (res))
129
+ isempty (res) ? error ( " Node $id does not exist. " ) : Node (first (res))
131
130
end
131
+ function Base. getindex (db:: DB , :: Colon )
132
+ res = execute (db, " SELECT * FROM nodes" )
133
+ isempty (res) ? error (" No nodes exist yet." ) : (Node (row) for row in res)
134
+ end
135
+
132
136
133
- function Base. push! (db:: DB , node:: Node )
134
- res = execute (db, " SELECT * FROM nodes WHERE id=?" , (node. id,))
135
- isempty (res) ?
136
- execute (db, " INSERT INTO nodes VALUES(?, ?, json(?))" , args (node)) :
137
- error (" Node with id=$(node. id) already exists in graph. Use `insert!` to overwrite." )
137
+ # -----------------------------------------------------------------------------# edges
138
+ function Base. push! (db:: DB , edge:: Edge ; upsert= false )
139
+ i, j = edge. source, edge. target
140
+ check = single_result_execute (db, " SELECT COUNT(*) FROM nodes WHERE id=? OR id=?" , (i, j))
141
+ (isnothing (check) || check < 2 ) && error (" Nodes $i and $j must exist in order for an edge to connect them." )
142
+ upsert ?
143
+ execute (db, " INSERT INTO edges VALUES(?, ?, ?, json(?)) ON CONFLICT(source,target,type) DO UPDATE SET props=excluded.props" , args (edge)) :
144
+ execute (db, " INSERT INTO edges VALUES(?, ?, ?, json(?))" , args (edge))
138
145
db
139
146
end
140
- function Base. insert! (db:: DB , node:: Node )
141
- execute (db, " INSERT INTO nodes VALUES(?, ?, json(?)) ON CONFLICT(id) DO UPDATE SET labels=excluded.labels, props=excluded.props" , args (node))
142
- db
147
+
148
+ function Base. getindex (db:: DB , i:: Integer , j:: Integer , type:: AbstractString )
149
+ res = execute (db, " SELECT * FROM edges WHERE source=? AND target=? AND type=?" , (i,j,type))
150
+ isempty (res) ? error (" Edge $i → $type → $j does not exist." ) : Edge (first (res))
151
+ end
152
+ function Base. getindex (db:: DB , i:: Integer , j:: Integer , :: Colon = Colon ())
153
+ res = execute (db, " SELECT * FROM edges WHERE source=? AND target=?" , (i, j))
154
+ isempty (res) ? error (" No edges connect nodes $i → $j ." ) : (Edge (row) for row in res)
143
155
end
144
156
157
+ function Base. getindex (db:: DB , :: Colon , j:: Integer , type:: AbstractString )
158
+ res = execute (db, " SELECT * FROM edges WHERE target=? AND type=?" , (j, type))
159
+ isempty (res) ? error (" No incoming edges $type → $j " ) : (Edge (row) for row in res)
160
+ end
161
+ function Base. getindex (db:: DB , i:: Colon , j:: Integer , :: Colon = Colon ())
162
+ res = execute (db, " SELECT * FROM edges WHERE target=?" , (j,))
163
+ isempty (res) ? error (" No incoming edges into node $j " ) : (Edge (row) for row in res)
164
+ end
145
165
166
+ function Base. getindex (db:: DB , i:: Integer , :: Colon , type:: AbstractString )
167
+ res = execute (db, " SELECT * FROM edges WHERE source=? AND type=?" , (i,type))
168
+ isempty (res) ? error (" No outgoing edges $type → $i " ) : (Edge (row) for row in res)
169
+ end
170
+ function Base. getindex (db:: DB , i:: Integer , :: Colon , :: Colon = Colon ())
171
+ res = execute (db, " SELECT * FROM edges WHERE source=?" , (i,))
172
+ isempty (res) ? error (" No outgoing edges from node $i " ) : (Edge (row) for row in res)
173
+ end
146
174
147
175
148
- # #-----------------------------------------------------------------------------# get/set nodes
149
- # function Base.setindex!(db::DB, props, id::Integer)
150
- # id ≤ length(db) + 1 || error("Cannot add node ID=$id to DB with $(length(db)) nodes. IDs must be added sequentially.")
151
- # execute(db, "INSERT INTO nodes VALUES(?, json(?))", (id, JSON3.write(props)))
152
- # db
153
- # end
154
- # function Base.getindex(db::DB, id::Integer)
155
- # res = single_result_execute(db, "SELECT props FROM nodes WHERE id = ?", (id,))
156
- # isnothing(res) ? throw(BoundsError(db, id)) : Node(id, res)
157
- # end
158
- # Base.getindex(db::DB, ids::AbstractArray{<:Integer}) = (getindex(db, id) for id in ids)
159
- # function Base.getindex(db::DB, ::Colon)
160
- # res = execute(db, "SELECT props from nodes")
161
- # (Node(i, row.props) for (i,row) in enumerate(res))
162
- # end
163
- # function Base.deleteat!(db::DB, id::Integer)
164
- # execute(db, "DELETE FROM nodes WHERE id = ?", (id,))
165
- # execute(db, "DELETE FROM edges WHERE source = ? OR target = ?", (id, id))
166
- # db
167
- # end
168
-
169
- # #-----------------------------------------------------------------------------# get/set edges
170
- # function Base.setindex!(db::DB, props, i::Integer, j::Integer)
171
- # execute(db, "INSERT INTO edges VALUES(?, ?, json(?))", (i, j, JSON3.write(props)))
172
- # db
173
- # end
174
- # function Base.getindex(db::DB, i::Integer, j::Integer)
175
- # res = single_result_execute(db, "SELECT props FROM edges WHERE source = ? AND target = ? ", (i,j))
176
- # isnothing(res) ? res : Edge(i, j, res)
177
- # end
178
- # Base.getindex(db::DB, i::Integer, js::AbstractArray{<:Integer}) = filter!(!isnothing, getindex.(db, i, js))
179
- # Base.getindex(db::DB, is::AbstractArray{<:Integer}, j::Integer) = filter!(!isnothing, getindex.(db, is, j))
180
- # function Base.getindex(db::DB, is::AbstractArray{<:Integer}, js::AbstractArray{<:Integer})
181
- # res = vcat((getindex(db, i, js) for i in is)...)
182
- # isempty(res) ? nothing : res
183
- # end
184
- # function Base.getindex(db::DB, i::Integer, ::Colon)
185
- # res = execute(db, "SELECT * FROM edges WHERE source=?", (i,))
186
- # isempty(res) ? nothing : (Edge(row...) for row in res)
187
- # end
188
- # function Base.getindex(db::DB, ::Colon, j::Integer)
189
- # res = execute(db, "SELECT * FROM edges WHERE target=?", (j,))
190
- # isempty(res) ? nothing : (Edge(row...) for row in res)
191
- # end
192
- # function Base.getindex(db::DB, ::Colon, ::Colon)
193
- # res = execute(db, "SELECT * from edges")
194
- # isempty(res) ? nothing : (Edge(row...) for row in res)
195
- # end
196
- # Base.getindex(db::DB, is::AbstractArray{<:Integer}, ::Colon) = filter!(!isnothing, getindex.(db, is, :))
197
- # Base.getindex(db::DB, ::Colon, js::AbstractArray{<:Integer}) = filter!(!isnothing, getindex.(db, :, js))
198
-
199
- # function Base.deleteat!(db::DB, i::Integer, j::Integer)
200
- # execute(db, "DELETE FROM edges WHERE source = ? AND target = ?", (i, j))
201
- # db
202
- # end
203
-
204
- # #-----------------------------------------------------------------------------# interfaces
205
- # Base.length(db::DB) = n_nodes(db)
206
- # Base.size(db::DB) = (nodes=n_nodes(db), edges=n_edges(db))
207
- # Base.lastindex(db::DB) = length(db)
208
- # Base.axes(db::DB, i) = size(db)[i]
209
-
210
- # Broadcast.broadcastable(db::DB) = Ref(db)
211
-
212
- # #-----------------------------------------------------------------------------# iterators
213
- # eachnode(db::DB) = (db[i] for i in 1:length(db))
214
- # eachedge(db::DB) = (Edge(row...) for row in execute(db, "SELECT * from edges"))
176
+ # -----------------------------------------------------------------------------# interfaces
177
+ Base. length (db:: DB ) = n_nodes (db)
178
+ Base. size (db:: DB ) = (nodes= n_nodes (db), edges= n_edges (db))
179
+ Base. lastindex (db:: DB ) = length (db)
180
+ Base. axes (db:: DB , i) = size (db)[i]
181
+
182
+ Broadcast. broadcastable (db:: DB ) = Ref (db)
215
183
216
184
end
0 commit comments