├── .gitattributes
├── .github
└── workflows
│ └── dotnet.yml
├── .gitignore
├── README.md
├── SliccDB.Tests
├── DatabaseConnectionTests.cs
├── SliccDB.Tests.csproj
└── data
│ └── sample.sliccdb
├── SliccDB.sln
├── SliccDB
├── Core
│ ├── ConnectionStatus.cs
│ ├── GraphEntity.cs
│ ├── Node.cs
│ ├── QueryResult.cs
│ ├── Relation.cs
│ └── Schema.cs
├── Database.cs
├── Exceptions
│ ├── RelationExistsException.cs
│ ├── SchemaExistsException.cs
│ ├── SchemaValidationException.cs
│ ├── SliccDbException.cs
│ └── TargetOrSourceNotFoundException.cs
├── Fluent
│ └── DatabaseConnectionExtensions.cs
├── SLICC_128.png
├── Serialization
│ └── DatabaseConnection.cs
└── SliccDB.csproj
├── license.md
└── screenshots
└── Explorer.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a .NET project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3 |
4 | name: .NET
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v3
21 | with:
22 | dotnet-version: 6.0.x
23 | - name: Restore dependencies
24 | run: dotnet restore
25 | - name: Build
26 | run: dotnet build --no-restore /property:Version=0.1.1.${{github.run_number}}
27 | - name: Test
28 | run: dotnet test --no-build --verbosity normal
29 | - name: Upload a Build Artifact
30 | uses: actions/upload-artifact@v3.1.2
31 | with:
32 | name: Latest
33 | path: ~/work/SliccDB/SliccDB/SliccDB/bin/Debug/SliccDB.0.1.1.${{github.run_number}}.nupkg
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SliccDB
2 |   
3 | 
4 |
5 | Light Embedded Graph Database for .net
6 |
7 |
8 | ## What is it?
9 | Think of it like SQLite for graph databases. There were some efforts to build something like this (such as Graph Engine) but Microsoft decided to abandon all of their graph database related projects. Be aware that I have no intention to make this some sort of Neo4J .net Clone. It will be not fully compliant with features of mentioned database.
10 |
11 | ## Installation
12 | You have three options to get this package.
13 | ### Nuget Package
14 | You can download nuget package from [here](https://www.nuget.org/packages/SliccDB/) or by using this command
15 | ```
16 | NuGet\Install-Package SliccDB
17 | ```
18 |
19 | ### Unstable Builds
20 | If you feel adventurous, you can download SliccDB as a build artifact from github actions. Note that these are not official releases, so they may contain bugs.
21 | But, they can also contain features not yet found on nuget... you never know ¯\_(ツ)_/¯
22 |
23 | ### Compiling from source
24 | You can always just clone this repo, open the solution with visual studio and compile it yourself. The build will produce both the dll and nuget package.
25 | You can also just build it with dotnet tool thingy. You're the boss here!
26 |
27 |
28 | ### Examples
29 | Here are some simple examples:
30 |
31 | #### Basic Operations
32 |
33 | ##### Create Node Programatically
34 |
35 | ```Csharp
36 | DatabaseConnection connection = new DatabaseConnection("filename");
37 | var properties = new Dictionary();
38 | var labels = new HashSet();
39 | properties.Add("Name", "Alice");
40 | labels.Add("Person");
41 |
42 | var nodeOne = connection.CreateNode(properties, labels);
43 | properties.Clear();
44 | labels.Clear();
45 | properties.Add("Name", "Steve");
46 | labels.Add("Person");
47 |
48 | var nodeTwo = connection.CreateNode(properties, labels);
49 | ```
50 |
51 | ##### Create Relations between two nodes
52 | Note that relations also have ability to hold data like Properties and Labels
53 | ```Csharp
54 | var properties = new Dictionary();
55 | var labels = new HashSet();
56 | properties.Add("How Much", "Very Much");
57 | labels.Add("Label on a node!");
58 | connection.CreateRelation(
59 | "Likes",
60 | sn => sn.First(x => x.Hash == nodeOne.Hash), tn => tn.First(a => a.Hash == nodeTwo.Hash), properties, labels);
61 | ```
62 |
63 | #### Queries
64 |
65 | You can query nodes and relations as you would any collection (With Linq).
66 |
67 | ###### Query Nodes
68 |
69 | ```Csharp
70 | var selectedNode = Connection.Nodes().Properties("Name".Value("Tom")).Labels("Person").FirstOrDefault();
71 | ```
72 |
73 | ###### Query Relations
74 |
75 | ```Csharp
76 | var queriedRelation = Connection.Relations().Properties("Property".Value("PropertyValue"))
77 | .Labels("RelationLabel");
78 | ```
79 |
80 |
81 | #### Mutations
82 |
83 | ##### Updates
84 |
85 | ```Csharp
86 | var relation = Connection.Relations("Likes").ToList().First();
87 | relation.Properties["How Much"] = "Not So Much";
88 | relation.Labels.Add("Love Hate Relationship");
89 |
90 | Connection.Update(relation);
91 |
92 | ...
93 |
94 | var node = Connection.Nodes().Properties("Name".Value("Steve")).First();
95 | node.Properties["Name"] = "Steve2";
96 |
97 | Connection.Update(node);
98 | ```
99 |
100 | ###### Deletions
101 |
102 | ```CSharp
103 | var toDelete = Connection.CreateNode(
104 | new Dictionary() { { "Name", "Tom" } },
105 | new HashSet() { "Person" }
106 | );
107 |
108 | var queryToDelete = Connection.Nodes().Properties("Name".Value("Tom")).Labels("Person").FirstOrDefault();
109 |
110 | Connection.Delete(queryToDelete);
111 | ```
112 | ```CSharp
113 | var testNode1 = Connection.CreateNode(
114 | new Dictionary() { { "Name", "C" } },
115 | new HashSet() { "S" }
116 | );
117 |
118 | var testNode2 = Connection.CreateNode(
119 | new Dictionary() { { "Name", "A" } },
120 | new HashSet() { "S" }
121 | );
122 |
123 | var properties = new Dictionary();
124 | var labels = new HashSet();
125 | properties.Add("Test", "Test");
126 | labels.Add("Test on a node!");
127 | Connection.CreateRelation("Test", sn => sn.First(x => x.Hash == testNode1.Hash), tn => tn.First(a => a.Hash == testNode2.Hash), properties, labels);
128 |
129 | var queriedRelation = Connection.Relations("Test").FirstOrDefault();
130 |
131 | Connection.Delete(queriedRelation);
132 | ```
133 |
134 | #### Schemas and Mappings
135 | Since 0.1.1 SliccDb features object - graph entity mapping, allowing to treat Relations and Nodes like regular poco classes.
136 | To ensure correct mapping for both types and property names it was neccessary to implement some sort of **schema enforcement**.Therefore, schemas were born.
137 |
138 | ##### Schemas
139 | Schemas are like recipes for your graph entities, they enforce the existence of certain fields and allow to specify to which type these fields should be mapped.
140 | Schemas are strongly related to labels. Schemas are defined per label.
141 |
142 | Example:
143 | ```Csharp
144 | schema = Connection.CreateSchema("Person",
145 | new List() { new() { FullTypeName = "System.String", Name = "Name" }, new() { FullTypeName = "System.Double", Name = "Age" } });
146 | ```
147 |
148 | Now when we label any entity with "Person", missing fields will be automatically added to it. In this example, when we add a label "Person" to a node or relation it will create Fields "Name" and "Age" if those do not exist.
149 |
150 | ##### Mapper
151 | Mapper allows us to manipulate graph by manipulating regular POCOs.
152 | Let's say we have two classes, representing Trains, Stations and a class RidesTo which represents relation between train and station:
153 | ```Csharp
154 | public class Train
155 | {
156 | public string SerialNumber { get; set; }
157 | }
158 | public class Station
159 | {
160 | public string City { get; set; }
161 | }
162 | public class RidesTo
163 | {
164 | public int Platform { get; set; }
165 | }
166 | ```
167 |
168 | By passing an object as an argument to method CreateNode we can create node with properties equal to the properties of an object:
169 | ```Csharp
170 | Connection.CreateNode(new Train() { SerialNumber = "CE2332" });
171 | Connection.CreateNode(new Train() { SerialNumber = "CE2356" });
172 | Connection.CreateNode(new Station() { City = "Katowice" });
173 | Connection.CreateNode(new Station() { City = "Warsaw" });
174 | ```
175 | We can also create a relation connecting two stations (by a query) with a train:
176 |
177 | ```Csharp
178 | Connection.CreateRelation(n =>
179 | Connection.Nodes().Properties("SerialNumber".Value("CE2332")).FirstOrDefault(),
180 | a => Connection.Nodes().Properties("SerialNumber".Value("CE2356")).FirstOrDefault(),
181 | new RidesTo(){ Platform = 2 });
182 | ```
183 |
184 | ### Why it exists?
185 | I just need embedded Graph Database Solution that supports Cypher Language.
186 | Graph DBMS is a perfect solution for some gamedev ai-related stuff (Procedural Behaviour Design, Perception and context awareness storage), but every solution needs a server. Imagine that in order to play your game you must install Neo4J's server in the first place, or every time your npc makes a decision it queries Azure Cloud to check how much he likes pizza. It's unreliable and forces you to host a game server for the entirety of a game's lifetime even though your game is singleplayer. Sounds stupid? Well, ask EA about Simcity 2013 - they thought it's a great idea! But really, Graph DBMS has other use cases. [Watch this video to know more about Graph DBMS](https://www.youtube.com/watch?v=GekQqFZm7mA)
187 |
188 | ### SliccDB Studio
189 | You can download SliccDB Studio [here](https://github.com/pmikstacki/SliccDBStudio).
190 |
191 | SliccDB Studio is a companion app currently used for interaction with database. It allows for CRUD operations on the database to test its' functionalities.
192 |
193 | 
194 |
195 |
196 |
197 | **DB Explorer is now considered obsolete, but you can still get the source code [here](https://github.com/pmikstacki/SliccDB.Explorer/)**
198 |
199 | ### Progress
200 | Below you'll find a list of features that will be included in the 1st milestone release of SliccDB:
201 | - [x] Basic Structures (Relations, Nodes)
202 | - [x] Serialization (Reading and Writing)
203 | - [x] Queries
204 | - [x] Basic Debugger App (Ability to add, remove, edit nodes and relations)
205 | - [x] Schemas for Nodes and Relations
206 | - [x] ORM (Object Relational Mapper) for Nodes and Relations
207 | - [x] New Companion App
208 | ### Can I help?
209 | Of course! Any help with the SliccDB will be appreciated.
210 | #### Special Thanks!
211 | - [Oleitao](https://github.com/oleitao) He wrote first unit tests of the database!
212 | - [HoLLy-Hacker (cool name tho) ](https://github.com/HoLLy-HaCKeR) - He performance tested key functionalities of SliccDB and found a few ~~errors~~ bugs.
213 |
--------------------------------------------------------------------------------
/SliccDB.Tests/DatabaseConnectionTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using NUnit.Framework;
3 | using SliccDB.Core;
4 | using SliccDB.Serialization;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Reflection;
10 | using SliccDB.Exceptions;
11 | using SliccDB.Fluent;
12 |
13 | namespace SliccDB.Tests
14 | {
15 | public class Tests
16 | {
17 | private const string fileName = "sample.siccdb";
18 |
19 | public Node NodeOne { get; set; }
20 |
21 | public Node NodeTwo { get; set; }
22 |
23 | public DatabaseConnection Connection { get; set; }
24 |
25 | [SetUp]
26 | public void SetupDatabase()
27 | {
28 | Setup();
29 | }
30 |
31 | [Test]
32 | public void CreateSchemaTest()
33 | {
34 | try
35 | {
36 | Connection.CreateSchema("Person",
37 | new List() { new() { FullTypeName = "System.String", Name = "Name" } });
38 | }
39 | catch (SchemaExistsException ex)
40 | {
41 | Assert.Fail(ex.Message);
42 | }
43 |
44 | Assert.IsTrue(Connection.Schemas.Exists(x => x.Label == "Person"));
45 | }
46 |
47 |
48 | [Test]
49 | public void CreateNodeTest()
50 | {
51 | CreateNodes();
52 |
53 | Assert.IsTrue(NodeOne.Labels.FirstOrDefault(x => x.Contains("Person")) != null, "Label Person not found.");
54 | Assert.IsTrue(NodeTwo.Labels.FirstOrDefault(x => x.Contains("Person")) != null, "Label Person not found.");
55 |
56 | Assert.IsTrue(NodeOne.Properties.FirstOrDefault(x => x.Key.Equals("Name")).Value.Equals("Alice"), "Property Name not found.");
57 | Assert.IsTrue(NodeTwo.Properties.FirstOrDefault(x => x.Key.Equals("Name")).Value.Equals("Steve"), "Property Name not found.");
58 | }
59 |
60 | [Test]
61 | public void CreateRelationsBetweenTwoNodesTest()
62 | {
63 | CreateNodes();
64 | CreateRelations();
65 |
66 | Assert.IsTrue(Connection.Relations.Any(x => x.RelationName.Equals("Likes")), "Relation not found.");
67 | }
68 |
69 | [Test]
70 | public void SelectNodeTest()
71 | {
72 | CreateNodes();
73 |
74 | var selectedNode = Connection.QueryNodes(x => x.Where(xa =>
75 | {
76 | xa.Properties.TryGetValue("Name", out var name);
77 | return name == "Steve";
78 | }).ToList()).First();
79 | Assert.IsTrue(selectedNode.Labels.Count > 0, "Node not found.");
80 | }
81 |
82 | [Test]
83 | public void SelectEdgeTest()
84 | {
85 | CreateNodes();
86 | CreateRelations();
87 |
88 | var selectedEdge = Connection.QueryRelations(x => x.Where(xa => xa.RelationName == "Likes").ToList()).First();
89 | Assert.IsTrue(selectedEdge.Labels.Count > 0, "Edge not found");
90 | }
91 |
92 | [Test]
93 | public void HaveNodesSchemaEnforced()
94 | {
95 |
96 | CreateNodes();
97 | CreateRelations();
98 | UpdateSchema();
99 | var nodesWithLabel = Connection.Nodes().Where(x => x.Labels.Contains("Person"));
100 | Assert.IsTrue(nodesWithLabel.ToList().Exists(x => x.Properties.ContainsKey("Age")));
101 | }
102 |
103 | [Test]
104 | public void SaveDatabase()
105 | {
106 | int nodes = 0;
107 | int relations = 0;
108 |
109 | CreateNodes();
110 | CreateRelations();
111 |
112 | nodes = Connection.Nodes.Count;
113 | relations = Connection.Relations.Count;
114 |
115 | Connection.SaveDatabase();
116 | Connection.CloseDatabase();
117 |
118 | Setup();
119 |
120 | Assert.IsTrue(Connection.Nodes.Count.Equals(nodes), "Saved nodes are different.");
121 | Assert.IsTrue(Connection.Relations.Count.Equals(relations), "Saved relations are different.");
122 | }
123 |
124 | private void CreateRelations()
125 | {
126 | var properties = new Dictionary();
127 | var labels = new HashSet();
128 | properties.Add("How Much", "Very Much");
129 | labels.Add("Label on a node!");
130 | Connection.CreateRelation("Likes", sn => sn.First(x => x.Hash == NodeOne.Hash), tn => tn.First(a => a.Hash == NodeTwo.Hash), properties, labels);
131 | }
132 |
133 | private void CreateNodes()
134 | {
135 | NodeOne = Connection.CreateNode(
136 | new Dictionary() { { "Name", "Alice"}, {"Age", "18"} },
137 | new HashSet() { "Person" }
138 | );
139 |
140 | NodeTwo = Connection.CreateNode(
141 | new Dictionary() { { "Name", "Steve" }, { "Age", "20" } },
142 | new HashSet() { "Person" }
143 | );
144 | }
145 |
146 | [Test]
147 | public void MapperGetNode()
148 | {
149 | MapperCreateNodes();
150 | var foundTrain = Connection.Nodes();
151 | Assert.IsTrue(foundTrain.Values.ToList().Exists(x => x.SerialNumber == "CE2356") && foundTrain.Values.ToList().Exists(x => x.SerialNumber == "CE2332"), foundTrain.Count.ToString());
152 | }
153 |
154 |
155 | [Test]
156 | public void MapperGetRelation()
157 | {
158 | Connection.ClearDatabase();
159 | MapperCreateNodes();
160 | MapperCreateRelation();
161 | var foundTrain = Connection.Relations();
162 | Assert.IsTrue(foundTrain.Values.ToList().Exists(x => x.Platform == 2));
163 | }
164 |
165 | private void MapperCreateNodes()
166 | {
167 | Connection.CreateNode(new Train() { SerialNumber = "CE2332" });
168 | Connection.CreateNode(new Train() { SerialNumber = "CE2356" });
169 | Connection.CreateNode(new Station() { City = "Katowice" });
170 | Connection.CreateNode(new Station() { City = "Warsaw" });
171 | }
172 |
173 | private void MapperCreateRelation()
174 | {
175 | Connection.CreateRelation(n =>
176 | Connection.Nodes().Properties("SerialNumber".Value("CE2332")).FirstOrDefault(),
177 | a => Connection.Nodes().Properties("SerialNumber".Value("CE2356")).FirstOrDefault(),
178 | new RidesTo(){ Platform = 2 });
179 | }
180 |
181 | [Test]
182 | public void TestNodeUpdateTest()
183 | {
184 | Connection.ClearDatabase();
185 | CreateNodes();
186 | CreateRelations();
187 | var node = Connection.Nodes().Properties("Name".Value("Steve")).First();
188 | node.Properties["Name"] = "Steve2";
189 | Connection.Update(node);
190 |
191 | var selectedNode = Connection.QueryNodes(Nodes => Nodes.Where(foundNode =>
192 | {
193 | foundNode.Properties.TryGetValue("Name", out var newName);
194 | return newName == "Steve2";
195 | }).ToList()).First();
196 | Assert.IsTrue(selectedNode != null, "Node not found.");
197 | }
198 |
199 |
200 | [Test]
201 | public void RelationUpdateTest()
202 | {
203 | Connection.ClearDatabase();
204 | CreateNodes();
205 | CreateRelations();
206 | UpdateSchema();
207 | var relation = Connection.Relations("Likes").ToList().First();
208 | relation.Properties["How Much"] = "Not So Much";
209 | relation.Labels.Add("Love Hate Relationship");
210 |
211 | Connection.Update(relation);
212 |
213 | var selectedRelation = Connection.QueryRelations(relations => relations.Where(foundRelation => foundRelation.RelationName == "Likes").ToList()).First();
214 | Assert.IsTrue(selectedRelation != null && selectedRelation.Properties["How Much"] == "Not So Much" && selectedRelation.Labels.Contains("Love Hate Relationship"), "Relation not found or properties weren't changed.");
215 | }
216 |
217 | [Test]
218 | public void NodeDeleteTest()
219 | {
220 | Connection.ClearDatabase();
221 | var toDelete = Connection.CreateNode(
222 | new Dictionary() { { "Name", "Tom" } , {"Age", "0"}},
223 | new HashSet() { "Person" }
224 | );
225 |
226 | var queryToDelete = Connection.Nodes()
227 | .Properties("Name".Value("Tom"))?
228 | .Labels("Person")
229 | .FirstOrDefault();
230 | Connection.Delete(queryToDelete);
231 |
232 | var queryToCheck = Connection.Nodes().Properties("Name".Value("Tom"))?.Labels("Person").FirstOrDefault();
233 |
234 | Assert.IsTrue(queryToCheck == null, "Node was not removed");
235 |
236 | }
237 |
238 |
239 | [Test]
240 | public void RelationDeleteTest()
241 | {
242 | var testNode1 = Connection.CreateNode(
243 | new Dictionary() { { "Name", "C" } },
244 | new HashSet() { "S" }
245 | );
246 |
247 | var testNode2 = Connection.CreateNode(
248 | new Dictionary() { { "Name", "A" } },
249 | new HashSet() { "S" }
250 | );
251 |
252 | var properties = new Dictionary();
253 | var labels = new HashSet();
254 | properties.Add("Test", "Test");
255 | labels.Add("Test on a node!");
256 | Connection.CreateRelation("Test", sn => sn.First(x => x.Hash == testNode1.Hash), tn => tn.First(a => a.Hash == testNode2.Hash), properties, labels);
257 |
258 | var queriedRelation = Connection.Relations("Test").FirstOrDefault();
259 |
260 | Connection.Delete(queriedRelation);
261 |
262 | var selectedRelation = Connection.QueryRelations(relations=> relations.Where(rel => rel.RelationName == "Test").ToList()).FirstOrDefault();
263 | Assert.IsTrue(selectedRelation == null, "Relation still exists");
264 | }
265 |
266 | public void UpdateSchema()
267 | {
268 | var schema = Connection.Schemas.FirstOrDefault(s => s.Label == "Person");
269 | if (schema == null)
270 | {
271 | schema = Connection.CreateSchema("Person",
272 | new List() { new() { FullTypeName = "System.String", Name = "Name" } });
273 | }
274 | schema.Properties.Add(new() { FullTypeName = "System.Double", Name = "Age" });
275 | Connection.UpdateSchema(schema);
276 | }
277 |
278 |
279 | private void Setup()
280 | {
281 | string filePath = Path.Combine(TestContext.CurrentContext.TestDirectory, fileName);
282 | Connection = new DatabaseConnection(filePath);
283 | }
284 |
285 |
286 | [OneTimeTearDown]
287 | public void TearDown()
288 | {
289 | Connection.ClearDatabase();
290 | }
291 | }
292 |
293 | public class Train
294 | {
295 | public string SerialNumber { get; set; }
296 | }
297 |
298 | public class Station
299 | {
300 | public string City { get; set; }
301 | }
302 | public class RidesTo
303 | {
304 | public int Platform { get; set; }
305 | }
306 | }
--------------------------------------------------------------------------------
/SliccDB.Tests/SliccDB.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/SliccDB.Tests/data/sample.sliccdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmikstacki/SliccDB/f5c1e7c18b4f3ed8fa6a12bc167cec89c46d7540/SliccDB.Tests/data/sample.sliccdb
--------------------------------------------------------------------------------
/SliccDB.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31912.275
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SliccDB", "SliccDB\SliccDB.csproj", "{204CCFA4-A64A-4405-B40A-A82F94B9E2FF}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SliccDB.Tests", "SliccDB.Tests\SliccDB.Tests.csproj", "{48709885-6B68-4553-8B39-87E191A7E34C}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Debug|x64.ActiveCfg = Debug|Any CPU
23 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Debug|x64.Build.0 = Debug|Any CPU
24 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Debug|x86.ActiveCfg = Debug|Any CPU
25 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Debug|x86.Build.0 = Debug|Any CPU
26 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Release|x64.ActiveCfg = Release|Any CPU
29 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Release|x64.Build.0 = Release|Any CPU
30 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Release|x86.ActiveCfg = Release|Any CPU
31 | {204CCFA4-A64A-4405-B40A-A82F94B9E2FF}.Release|x86.Build.0 = Release|Any CPU
32 | {48709885-6B68-4553-8B39-87E191A7E34C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {48709885-6B68-4553-8B39-87E191A7E34C}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {48709885-6B68-4553-8B39-87E191A7E34C}.Debug|x64.ActiveCfg = Debug|Any CPU
35 | {48709885-6B68-4553-8B39-87E191A7E34C}.Debug|x64.Build.0 = Debug|Any CPU
36 | {48709885-6B68-4553-8B39-87E191A7E34C}.Debug|x86.ActiveCfg = Debug|Any CPU
37 | {48709885-6B68-4553-8B39-87E191A7E34C}.Debug|x86.Build.0 = Debug|Any CPU
38 | {48709885-6B68-4553-8B39-87E191A7E34C}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {48709885-6B68-4553-8B39-87E191A7E34C}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {48709885-6B68-4553-8B39-87E191A7E34C}.Release|x64.ActiveCfg = Release|Any CPU
41 | {48709885-6B68-4553-8B39-87E191A7E34C}.Release|x64.Build.0 = Release|Any CPU
42 | {48709885-6B68-4553-8B39-87E191A7E34C}.Release|x86.ActiveCfg = Release|Any CPU
43 | {48709885-6B68-4553-8B39-87E191A7E34C}.Release|x86.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(ExtensibilityGlobals) = postSolution
49 | SolutionGuid = {76D275D1-E6B0-4A6C-9E64-B6E9DE9273D5}
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/SliccDB/Core/ConnectionStatus.cs:
--------------------------------------------------------------------------------
1 | namespace SliccDB.Core
2 | {
3 | public enum ConnectionStatus
4 | {
5 | Connected,
6 | NotConnected
7 | }
8 | }
--------------------------------------------------------------------------------
/SliccDB/Core/GraphEntity.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using MessagePack;
3 |
4 | namespace SliccDB.Core
5 | {
6 | [MessagePack.Union(0, typeof(Node))]
7 | [MessagePack.Union(1, typeof(Relation))]
8 | public abstract class GraphEntity
9 | {
10 | ///
11 | /// Properties contained in an entity
12 | ///
13 | [Key(0)]
14 | public Dictionary Properties { get; set; } = new Dictionary();
15 | ///
16 | /// Labels contained in an entity
17 | ///
18 | [Key(1)]
19 |
20 | public HashSet Labels { get; set; } = new HashSet();
21 |
22 | [Key(2)]
23 | public virtual string Hash { get; set; }
24 | }
25 | }
--------------------------------------------------------------------------------
/SliccDB/Core/Node.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.Text;
5 | using MessagePack;
6 |
7 | namespace SliccDB.Core
8 | {
9 | ///
10 | /// Represents a node in a graph
11 | ///
12 | [MessagePackObject]
13 | public class Node : GraphEntity
14 | {
15 | public Node()
16 | {
17 | Hash = Guid.NewGuid().ToString();
18 | }
19 |
20 | public Node(Dictionary properties, HashSet labels)
21 | {
22 | Hash = Guid.NewGuid().ToString();
23 | Properties = properties;
24 | Labels = labels;
25 | }
26 |
27 | public Node(string hash, Dictionary properties, HashSet labels)
28 | {
29 | Hash = hash;
30 | Properties = properties;
31 | Labels = labels;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/SliccDB/Core/QueryResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace SliccDB.Core
4 | {
5 | public class QueryResult
6 | {
7 | public HashSet Nodes { get; set; } = new HashSet();
8 | public HashSet Relations { get; set; } = new HashSet();
9 | }
10 | }
--------------------------------------------------------------------------------
/SliccDB/Core/Relation.cs:
--------------------------------------------------------------------------------
1 | using MessagePack;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace SliccDB.Core
6 | {
7 | ///
8 | /// Edge of the graph
9 | ///
10 | [MessagePackObject]
11 | public class Relation : GraphEntity, IComparable, IEquatable
12 | {
13 | [Key(3)]
14 | public virtual string RelationName { get; set; }
15 | [Key(4)]
16 | public virtual string SourceHash { get; set; }
17 | [Key(5)]
18 | public virtual string TargetHash { get; set; }
19 |
20 |
21 | public Relation()
22 | {
23 | Hash = Guid.NewGuid().ToString();
24 | }
25 |
26 | public Relation(string relationName, Dictionary properties, HashSet labels, string sourceHash, string targetHash)
27 | {
28 | Hash = Guid.NewGuid().ToString();
29 | RelationName = relationName;
30 | Properties = properties;
31 | Labels = labels;
32 | SourceHash = sourceHash;
33 | TargetHash = targetHash;
34 | }
35 |
36 | public int CompareTo(Relation other)
37 | {
38 | if (ReferenceEquals(this, other)) return 0;
39 | if (ReferenceEquals(null, other)) return 1;
40 | var relationNameComparison = string.Compare(RelationName, other.RelationName, StringComparison.Ordinal);
41 | if (relationNameComparison != 0) return relationNameComparison;
42 | var sourceHashComparison = string.Compare(SourceHash, other.SourceHash, StringComparison.Ordinal);
43 | if (sourceHashComparison != 0) return sourceHashComparison;
44 | return string.Compare(TargetHash, other.TargetHash, StringComparison.Ordinal);
45 | }
46 |
47 | public bool Equals(Relation other)
48 | {
49 | if (ReferenceEquals(null, other)) return false;
50 | if (ReferenceEquals(this, other)) return true;
51 | return RelationName == other.RelationName && Equals(Properties, other.Properties) && Equals(Labels, other.Labels) && SourceHash == other.SourceHash && TargetHash == other.TargetHash;
52 | }
53 |
54 | public override bool Equals(object obj)
55 | {
56 | if (ReferenceEquals(null, obj)) return false;
57 | if (ReferenceEquals(this, obj)) return true;
58 | if (obj.GetType() != this.GetType()) return false;
59 | return Equals((Relation) obj);
60 | }
61 |
62 | }
63 | }
--------------------------------------------------------------------------------
/SliccDB/Core/Schema.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MessagePack;
3 | using System.Collections.Generic;
4 |
5 | namespace SliccDB.Core;
6 | ///
7 | /// Represents schema with needed properties
8 | ///
9 | [MessagePackObject]
10 | public class Schema
11 | {
12 |
13 | [Key(0)]
14 | public virtual string Label { get; set; }
15 |
16 | [Key(2)]
17 | public List Properties { get; set; } = new List();
18 | }
19 |
20 | [MessagePackObject]
21 | public class Property
22 | {
23 |
24 | [Key(0)]
25 | public string Name { get; set; }
26 |
27 | [Key(2)]
28 | public string FullTypeName { get; set; }
29 |
30 | public Property()
31 | {
32 |
33 | }
34 |
35 | public Property(string name, Type type)
36 | {
37 | Name = name;
38 | FullTypeName = type.FullName;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/SliccDB/Database.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using MessagePack;
5 | using SliccDB.Core;
6 | using MessagePack;
7 |
8 | namespace SliccDB
9 | {
10 | ///
11 | /// A graph Database
12 | ///
13 | [MessagePackObject]
14 | public class Database : IDisposable
15 | {
16 | [Key(0)]
17 | public HashSet Nodes { get; set; } = new HashSet();
18 | [Key(1)]
19 | public HashSet Relations { get; set; }= new HashSet();
20 | [Key(2)]
21 | public List Schemas { get; set; } = new List();
22 | public void Dispose()
23 | {
24 | Dispose(true);
25 | GC.SuppressFinalize(this);
26 | }
27 |
28 | protected virtual void Dispose(bool disposing)
29 | {
30 | if (disposing)
31 | {
32 | // free managed resources
33 | Nodes.Clear();
34 | Relations.Clear();
35 | Schemas.Clear();
36 | }
37 | // free native resources if there are any.
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/SliccDB/Exceptions/RelationExistsException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SliccDB.Exceptions
4 | {
5 | public class RelationExistsException : SliccDbException
6 | {
7 | public RelationExistsException(string targetHash, string sourceHash) : base()
8 | {
9 | Message = $"Relation with Target Hash {targetHash} and Source Hash {sourceHash} already exists!";
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/SliccDB/Exceptions/SchemaExistsException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using SliccDB.Core;
3 |
4 | namespace SliccDB.Exceptions;
5 |
6 | public class SchemaExistsException : SliccDbException
7 | {
8 | public override string HelpLink { get; set; }
9 |
10 | public SchemaExistsException(string schemaName) : base()
11 | {
12 | Message = $"Schema for label {schemaName} already exists!";
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/SliccDB/Exceptions/SchemaValidationException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace SliccDB.Exceptions;
6 |
7 | public class SchemaValidationException : SliccDbException
8 | {
9 | public SchemaValidationException(string label, Dictionary properties)
10 | {
11 | StringBuilder sb = new StringBuilder();
12 | sb.AppendLine($"Schema validation for {label} failed. Problematic fields: ");
13 | foreach (var property in properties)
14 | {
15 | sb.AppendLine($"Property: {property.Key}, Reason: {property.Value}");
16 | }
17 |
18 | Message = sb.ToString();
19 | }
20 | }
--------------------------------------------------------------------------------
/SliccDB/Exceptions/SliccDbException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SliccDB.Exceptions;
4 |
5 | public class SliccDbException : Exception
6 | {
7 | public string Message { get; protected set; }
8 |
9 | }
--------------------------------------------------------------------------------
/SliccDB/Exceptions/TargetOrSourceNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SliccDB.Exceptions
4 | {
5 | public class TargetOrSourceNotFoundException : SliccDbException
6 | {
7 | public TargetOrSourceNotFoundException()
8 | {
9 | Message = " or node with provided hashes doesn't exist";
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/SliccDB/Fluent/DatabaseConnectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using SliccDB.Core;
5 | using SliccDB.Serialization;
6 |
7 | namespace SliccDB.Fluent;
8 |
9 | public static class DatabaseConnectionExtensions
10 | {
11 | public static HashSet Nodes(this DatabaseConnection db)
12 | {
13 | return db.Nodes;
14 | }
15 | ///
16 | /// Returns all nodes in given connection that match the structure of
17 | ///
18 | /// Type used to match against properties in the
19 | /// Database connection which will be queried
20 | /// A dictionary with ids and the nodes of type themselves
21 | public static Dictionary Nodes(this DatabaseConnection db)
22 | {
23 | if (!db.Schemas.Exists(s => s.Label == typeof(T).Name))
24 | return new Dictionary();
25 |
26 | var filledObjects = new Dictionary();
27 | foreach (var node in db.Nodes().Where(x => x.Labels.Contains(typeof(T).Name)))
28 | {
29 | var newTypeInstance = (T) Activator.CreateInstance(typeof(T));
30 |
31 | db.FillObjectFromProperties(node, newTypeInstance);
32 | filledObjects.Add(node.Hash, newTypeInstance);
33 | }
34 |
35 | return filledObjects;
36 | }
37 | ///
38 | /// Returns all relations in given connection that match the structure of
39 | ///
40 | /// Type used to match against properties in the
41 | /// Database connection which will be queried
42 | /// A dictionary with ids and the relations of type themselves
43 | public static Dictionary Relations(this DatabaseConnection db)
44 | {
45 | if (!db.Schemas.Exists(s => s.Label == typeof(T).Name))
46 | return new Dictionary();
47 |
48 | var filledObjects = new Dictionary();
49 | foreach (var node in db.Relations().Where(x => x.RelationName == typeof(T).Name))
50 | {
51 | var newTypeInstance = (T) Activator.CreateInstance(typeof(T));
52 |
53 | db.FillObjectFromProperties(node, newTypeInstance);
54 | filledObjects.Add(node.Hash, (T)newTypeInstance);
55 | }
56 |
57 | return filledObjects;
58 | }
59 | ///
60 | /// Returns all entities in given connection that match the structure of
61 | ///
62 | /// Type used to match against properties in the
63 | /// Database connection which will be queried
64 | /// A dictionary with ids and the entities of type themselves
65 | public static Dictionary Entities(this DatabaseConnection db)
66 | {
67 | if (!db.Schemas.Exists(s => s.Label == typeof(T).Name))
68 | return new Dictionary();
69 |
70 | var filledObjects = new Dictionary();
71 | foreach (var node in db.Entities().Where(x => x.Labels.Contains(typeof(T).Name)))
72 | {
73 | var newTypeInstance = (T) Activator.CreateInstance(typeof(T));
74 |
75 | db.FillObjectFromProperties(node, newTypeInstance);
76 | filledObjects.Add(node.Hash, (T)newTypeInstance);
77 | }
78 |
79 | return filledObjects;
80 | }
81 |
82 | ///
83 | /// Returns all relations in the database
84 | ///
85 | /// Database Connection
86 | /// All relations in the database
87 | public static HashSet Relations(this DatabaseConnection db)
88 | {
89 | var edges = new HashSet();
90 | foreach (var edge in db.Relations)
91 | edges.Add(edge);
92 | return edges;
93 | }
94 | ///
95 | /// Returns all relations in the database with
96 | ///
97 | ///
98 | ///
99 | /// All Relations with
100 | public static HashSet Relations(this DatabaseConnection db, string name)
101 | {
102 | var edges = new HashSet();
103 | foreach (var edge in db.Relations.Where(x => x.RelationName == name))
104 | edges.Add(edge);
105 | return edges;
106 | }
107 | ///
108 | /// Returns all entities in connection
109 | ///
110 | /// connection
111 | /// All Entities
112 | public static HashSet Entities(this DatabaseConnection db)
113 | {
114 | var entities = new HashSet();
115 | foreach (var entity in db.Nodes())
116 | entities.Add(entity);
117 | foreach (var entity in db.Relations())
118 | entities.Add(entity);
119 | return entities;
120 | }
121 |
122 | ///
123 | /// Replaces the node in the database
124 | ///
125 | /// database
126 | /// entity to replace
127 | /// throws if node with given hash was not found
128 | public static void Replace(this HashSet db, Node entity)
129 | {
130 | var numRemoved = db.RemoveWhere(x => x.Hash == entity.Hash);
131 | if (numRemoved == 1)
132 | db.Add(entity);
133 | else
134 | {
135 | throw new InvalidOperationException("Attempted to replace a node that does not exist");
136 | }
137 | }
138 | ///
139 | /// Replaces the relation in the database
140 | ///
141 | /// database
142 | /// entity to replace
143 | /// throws if relation with given hash was not found
144 | public static void Replace(this HashSet db, Relation entity)
145 | {
146 | var numRemoved = db.RemoveWhere(x => x.Hash == entity.Hash);
147 | if (numRemoved == 1)
148 | db.Add(entity);
149 | else
150 | {
151 | throw new InvalidOperationException("Attempted to replace a relation that does not exist");
152 | }
153 | }
154 | ///
155 | /// Founds an entity with all labels
156 | ///
157 | /// all entities to perform query onto
158 | /// all labels to query by
159 | ///
160 | public static HashSet Labels(this HashSet allEntities, params string[] labels)
161 | {
162 | var entities = new HashSet();
163 |
164 | foreach (var entity in allEntities)
165 | {
166 | var commonLabels = entity.Labels.Count(x => labels.Contains(x));
167 | if (commonLabels == entity.Labels.Count)
168 | entities.Add(entity);
169 | }
170 |
171 | return entities;
172 | }
173 |
174 | ///
175 | /// Finds entity with properties and their values
176 | ///
177 | /// all entities to perform query onto
178 | /// all properties to query by
179 | ///
180 | public static HashSet Properties(this HashSet allEntities, params KeyValuePair[] properties)
181 | {
182 | var entities = new HashSet();
183 | foreach(var entity in allEntities)
184 | {
185 | foreach (var keyValuePair in properties)
186 | {
187 | if (!entity.Properties.ContainsKey(keyValuePair.Key)) return null;
188 |
189 | if (entity.Properties[keyValuePair.Key] == keyValuePair.Value)
190 | {
191 | entities.Add(entity);
192 | }
193 | }
194 | };
195 | return entities;
196 | }
197 |
198 | ///
199 | /// Finds entity with properties and their values
200 | ///
201 | /// all entities to perform query onto
202 | /// all properties to query by
203 | ///
204 | public static HashSet Labels(this HashSet allEntities, params string[] labels) where T : GraphEntity
205 | {
206 | var entities = new HashSet();
207 |
208 | foreach (var entity in allEntities)
209 | {
210 |
211 | var commonLabels = entity.Labels.Count(x => labels.Contains(x));
212 | if (commonLabels == labels.Length)
213 | entities.Add(entity);
214 |
215 | }
216 |
217 | return entities;
218 | }
219 |
220 | ///
221 | /// Finds entities with properties
222 | ///
223 | ///
224 | /// all entities in database
225 | /// properties' names and values to match against
226 | ///
227 | public static HashSet Properties(this HashSet allEntities,
228 | params KeyValuePair[] properties) where T : GraphEntity
229 | {
230 | var entities = new HashSet();
231 | foreach (var entity in allEntities)
232 | {
233 | foreach (var keyValuePair in properties)
234 | {
235 | if (entity.Properties.ContainsKey(keyValuePair.Key)
236 | && entity.Properties[keyValuePair.Key] == keyValuePair.Value)
237 | {
238 | entities.Add(entity);
239 | }
240 | }
241 | }
242 |
243 | return entities;
244 | }
245 | ///
246 | /// Returns all nodes that have outgoing relation ([N]->) with given name
247 | ///
248 | /// database connection
249 | /// name of the relation that should be outgoing
250 | ///
251 | public static HashSet Out(this DatabaseConnection connection, string relationName)
252 | {
253 | var allRelationsWithName = connection.Relations(relationName);
254 | var entities = connection.Nodes().Where(n => allRelationsWithName.ToList().Exists(r => r.SourceHash == n.Hash));
255 | return new HashSet(entities);
256 | }
257 |
258 | ///
259 | /// Returns all nodes that have incoming relation (->[N]) with given name
260 | ///
261 | /// database connection
262 | /// name of the relation that should be incoming
263 | ///
264 | public static HashSet In(this DatabaseConnection connection, string relationName)
265 | {
266 | var allRelationsWithName = connection.Relations(relationName);
267 | var entities = connection.Nodes().Where(n => allRelationsWithName.ToList().Exists(r => r.TargetHash == n.Hash));
268 | return new HashSet(entities);
269 | }
270 |
271 |
272 | public static KeyValuePair Value(this string key, string value)
273 | {
274 | return new KeyValuePair(key, value);
275 | }
276 |
277 |
278 | }
--------------------------------------------------------------------------------
/SliccDB/SLICC_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmikstacki/SliccDB/f5c1e7c18b4f3ed8fa6a12bc167cec89c46d7540/SliccDB/SLICC_128.png
--------------------------------------------------------------------------------
/SliccDB/Serialization/DatabaseConnection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Security.Cryptography.X509Certificates;
8 | using deniszykov.TypeConversion;
9 | using MessagePack;
10 | using SliccDB.Core;
11 | using SliccDB.Exceptions;
12 | using SliccDB.Fluent;
13 |
14 | namespace SliccDB.Serialization
15 | {
16 |
17 | public class DatabaseConnection
18 | {
19 | public string FilePath { get; private set; }
20 |
21 | internal Database Database { get; private set; }
22 |
23 | public HashSet Nodes => Database?.Nodes;
24 | public HashSet Relations => Database?.Relations;
25 | public List Schemas => Database?.Schemas;
26 |
27 | public ConnectionStatus ConnectionStatus { get; set; }
28 | private readonly bool realtime;
29 |
30 | private TypeConversionProvider typeConversionProvider;
31 |
32 | ///
33 | /// Creates new Database Connection Instance
34 | ///
35 | /// Path to a database file
36 | /// if true, attempts to save the database on every operation. Disabled by default as it is memory intensive
37 | public DatabaseConnection(string filePath, bool realtime = false)
38 | {
39 | FilePath = filePath;
40 | if (File.Exists(filePath))
41 | {
42 | var bytes = File.ReadAllBytes(filePath);
43 | try
44 | {
45 | Database = MessagePackSerializer.Deserialize(bytes);
46 | this.ConnectionStatus = ConnectionStatus.Connected;
47 | }
48 | catch (Exception exception)
49 | {
50 | Database = new Database();
51 | this.ConnectionStatus = ConnectionStatus.Connected;
52 | }
53 | }
54 | else
55 | {
56 | Database = new Database();
57 | this.ConnectionStatus = ConnectionStatus.Connected;
58 | var bytes = MessagePackSerializer.Serialize(Database);
59 | File.WriteAllBytes(filePath, bytes);
60 | }
61 | this.realtime = realtime;
62 |
63 | //init type conversion provider
64 | typeConversionProvider = new TypeConversionProvider();
65 | typeConversionProvider.RegisterConversion((s, s1, arg3) => s, ConversionQuality.Constructor);
66 | }
67 | ///
68 | /// Queries nodes based on generic delegate type
69 | ///
70 | /// generic delegate type
71 | /// Queried Nodes
72 | public IEnumerable QueryNodes(Func, IEnumerable> query)
73 | {
74 | return query.Invoke(Nodes);
75 | }
76 | ///
77 | /// Queries relations based on generic delegate type
78 | ///
79 | /// generic delegate type
80 | /// Queried Relations
81 | public IEnumerable QueryRelations(Func, IEnumerable> query)
82 | {
83 | return query.Invoke(Relations);
84 | }
85 |
86 | ///
87 | /// Updates Node or Relation
88 | ///
89 | ///
90 | /// Throws when wrong type of GraphEntity is supplied
91 | public void Update(GraphEntity entity)
92 | {
93 | ValidateSchema(entity);
94 | if (entity is Node node)
95 | {
96 | Nodes.Replace(node);
97 | }
98 | else if (entity is Relation relation)
99 | {
100 | Relations.Replace(relation);
101 | }
102 | else
103 | {
104 | throw new InvalidOperationException("Invalid entity type");
105 | }
106 | SaveDatabase();
107 | }
108 |
109 | ///
110 | /// Enforces a schema based on amount of schemas
111 | ///
112 | /// entity to check schema enforcement on
113 | /// Throws when schema validation for given label fails
114 | private void ValidateSchema(GraphEntity entity)
115 | {
116 | var schema = Schemas.FirstOrDefault(s => entity.Labels.Contains(s.Label));
117 |
118 | if (schema == null)
119 | return;
120 |
121 | Dictionary reasons = new Dictionary();
122 | bool result = true;
123 | foreach (var schemaProperty in schema.Properties)
124 | {
125 | if (!entity.Properties.TryGetValue(schemaProperty.Name, out var foundProperty))
126 | {
127 | reasons.Add(schemaProperty.Name, "Not Found");
128 | result = false;
129 | }
130 |
131 | if (foundProperty != null && !typeConversionProvider.TryConvert(typeof(string), Type.GetType(schemaProperty.FullTypeName),
132 | foundProperty, out var convertResult))
133 | {
134 | reasons.Add(schemaProperty.Name,
135 | "Converter not registered, try registering one with RegisterConversion");
136 | result = false;
137 | }
138 | }
139 |
140 | if (!result)
141 | {
142 | throw new SchemaValidationException(schema.Label, reasons);
143 | }
144 | }
145 |
146 | ///
147 | /// Bulk updates Node or Relation
148 | ///
149 | /// collection of entities to update
150 | /// Throws when wrong type of GraphEntity is supplied
151 | public void BulkUpdate(List entities)
152 | {
153 | foreach (var entity in entities)
154 | {
155 | if (entity is Node node)
156 | {
157 | Nodes.Replace(node);
158 | }
159 | else if (entity is Relation relation)
160 | {
161 | Relations.Replace(relation);
162 | }
163 | else
164 | {
165 | throw new InvalidOperationException("Invalid entity type");
166 | }
167 | }
168 | SaveDatabase();
169 | }
170 | ///
171 | /// Deletes any entity passed and related nodes
172 | ///
173 | /// entity to delete
174 | /// all deleted entities
175 | public int Delete(GraphEntity entity)
176 | {
177 | var nodes = Nodes.RemoveWhere(n => n.Hash == entity.Hash);
178 | var relationsLinkedWithNodes = Relations.RemoveWhere(r => r.SourceHash == entity.Hash || r.TargetHash == entity.Hash);
179 | var relations = Relations.RemoveWhere(r => r.Hash == entity.Hash);
180 |
181 | if (realtime) SaveDatabase();
182 |
183 | return nodes + relationsLinkedWithNodes + relations;
184 | }
185 | ///
186 | /// Find nodes with relation with name as it's source (node->relation)
187 | ///
188 | /// name of the relation
189 | /// all nodes with relation as their source
190 | public IEnumerable FindNodesWithRelationSource(string relationName)
191 | {
192 | var relationsSources = Relations.AsParallel().Where(x => x.RelationName == relationName).Select(x => x.SourceHash);
193 | return Nodes.AsParallel().Where(x => relationsSources.Contains(x.Hash));
194 | }
195 | ///
196 | /// Find nodes with relation with name as it's target (relation->node)
197 | ///
198 | /// name of the relation
199 | /// all nodes with relation as their target
200 | public IEnumerable FindNodesWithRelationTarget(string relationName)
201 | {
202 | var relationsSources = Relations.AsParallel().Where(x => x.RelationName == relationName).Select(x => x.TargetHash);
203 | return Nodes.AsParallel().Where(x => relationsSources.Contains(x.Hash));
204 | }
205 | ///
206 | /// Saves a database
207 | ///
208 | public void SaveDatabase()
209 | {
210 | if (File.Exists(FilePath))
211 | {
212 | var bytes = MessagePackSerializer.Serialize(Database);
213 | File.WriteAllBytes(FilePath, bytes);
214 | }
215 | }
216 | ///
217 | /// Closes database
218 | ///
219 | public void CloseDatabase()
220 | {
221 | if (Database != null && ConnectionStatus == ConnectionStatus.Connected)
222 | {
223 | this.ConnectionStatus = ConnectionStatus.NotConnected;
224 | Database.Dispose();
225 | }
226 | }
227 | ///
228 | /// Clears database
229 | ///
230 | public void ClearDatabase()
231 | {
232 | Nodes.Clear();
233 | Relations.Clear();
234 | Schemas.Clear();
235 | SaveDatabase();
236 | }
237 | ///
238 | /// Creates a new node with given properties and labels
239 | ///
240 | /// properties
241 | /// labels
242 | ///
243 | public Node CreateNode(Dictionary properties = null, HashSet labels = null)
244 | {
245 | Dictionary props = new Dictionary();
246 | HashSet lablSet = new HashSet();
247 | var node = new Node(properties ?? props, labels ?? lablSet);
248 | ValidateSchema(node);
249 | Database.Nodes.Add(node);
250 | if(realtime) SaveDatabase();
251 | return node;
252 | }
253 | ///
254 | /// Creates a new schema
255 | ///
256 | ///
257 | ///
258 | ///
259 | ///
260 | public Schema CreateSchema(string label, List properties)
261 | {
262 | if (Schemas.Exists(x => x.Label == label))
263 | {
264 | throw new SchemaExistsException(label);
265 | }
266 |
267 | Schema schema = new Core.Schema();
268 | schema.Label = label;
269 | schema.Properties = properties;
270 | Schemas.Add(schema);
271 | UpdateEntitiesForSchema(schema);
272 | if (realtime) SaveDatabase();
273 | return schema;
274 | }
275 | ///
276 | /// Creates a new schema
277 | ///
278 | /// Schema to create
279 | /// Schema added to database
280 | /// Throws if schema exists
281 | public Schema CreateSchema(Schema schema)
282 | {
283 | if (Schemas.Exists(x => x.Label == schema.Label))
284 | {
285 | throw new SchemaExistsException(schema.Label);
286 | }
287 | Schemas.Add(schema);
288 | UpdateEntitiesForSchema(schema);
289 | if (realtime) SaveDatabase();
290 | return schema;
291 | }
292 | ///
293 | /// Deletes Schema while retaining entity values
294 | ///
295 | /// schema to delete
296 | public void DeleteSchema(Schema schema)
297 | {
298 | Schemas.Remove(schema);
299 | }
300 |
301 | ///
302 | /// Deletes schema using label
303 | ///
304 | /// label for the schema
305 | /// throws if schema with given label was not found
306 | public void DeleteSchemaByLabel(string schemaLabel)
307 | {
308 | var schema = Schemas.FirstOrDefault(s => s.Label == schemaLabel);
309 | if (schema is null)
310 | throw new InvalidOperationException($"Schema with label {schemaLabel} does not exist!");
311 | Schemas.Remove(schema);
312 |
313 | }
314 |
315 | ///
316 | /// Updates Schema that already exists
317 | ///
318 | /// Schema to update
319 | /// Updated schema
320 | /// Throws if schema for this label doesn't exist
321 | public Schema UpdateSchema(Schema schema)
322 | {
323 | var foundSchema = Schemas.FirstOrDefault(x => x.Label == schema.Label);
324 | if (foundSchema == null)
325 | throw new InvalidOperationException("Schema was not found. Use CreateSchema instead");
326 |
327 | Schemas.Remove(foundSchema);
328 |
329 | Schemas.Add(schema);
330 | UpdateEntitiesForSchema(schema);
331 | if (realtime) SaveDatabase();
332 | return schema;
333 | }
334 | ///
335 | /// Creates relation entity
336 | ///
337 | /// relation name
338 | /// source node search func
339 | /// target node search func
340 | /// properties in relation
341 | /// labels in relation
342 | /// Throws if relation already exists
343 | public void CreateRelation(string relationName, Func, Node> sourceNode, Func, Node> targetNode, Dictionary properties = null, HashSet labels = null)
344 | {
345 | var sourceNodeObject = sourceNode.Invoke(Database.Nodes);
346 | var targetNodeObject = targetNode.Invoke(Database.Nodes);
347 | if (sourceNodeObject is null || targetNodeObject is null)
348 | {
349 |
350 | }
351 | bool exists = Relations.AsParallel().ToList().Exists(x =>
352 | x.TargetHash == targetNodeObject.Hash && x.SourceHash == sourceNodeObject.Hash);
353 | if (exists)
354 | throw new RelationExistsException(targetNodeObject.Hash, targetNodeObject.Hash);
355 | Dictionary props = new Dictionary();
356 | HashSet lablSet = new HashSet();
357 | var relation = new Relation(relationName, properties ?? props, labels ?? lablSet, sourceNodeObject.Hash,
358 | targetNodeObject.Hash);
359 | ValidateSchema(relation);
360 | Database.Relations.Add(relation);
361 | if (realtime) SaveDatabase();
362 |
363 | }
364 |
365 | ///
366 | /// Registers custom converter using TypeConversion library. More Info />
367 | ///
368 | /// Type from
369 | /// Type to
370 | /// conversion function
371 | /// quality of the conversion. Click more info in summary
372 | public void RegisterConversion(
373 | Func conversionFunc,
374 | ConversionQuality quality)
375 | {
376 | typeConversionProvider.RegisterConversion(conversionFunc, quality);
377 |
378 | }
379 | ///
380 | /// Updates entities by inserting properties enforced by a given schema
381 | ///
382 | /// Schema to enforce
383 | private void UpdateEntitiesForSchema(Schema schema)
384 | {
385 | var nodesForSchema = Nodes.Where(x => x.Labels.Contains(schema.Label)) as IEnumerable;
386 | var relationsForSchema = Relations.Where(x => x.Labels.Contains(schema.Label)) as IEnumerable;
387 | var entities = new List();
388 | entities.AddRange(nodesForSchema);
389 | entities.AddRange(relationsForSchema);
390 | foreach (var entity in entities)
391 | {
392 | UpdateMissingFieldsForEntity(entity, schema);
393 | }
394 |
395 | BulkUpdate(entities);
396 | }
397 | ///
398 | /// Updates entity by filling in missing fields from schemas
399 | ///
400 | /// entity to update
401 | private void UpdateSchemasForEntity(GraphEntity entity)
402 | {
403 | var SchemasForEntity = Schemas.Where(s => entity.Labels.Contains(s.Label));
404 | foreach (var schema in SchemasForEntity)
405 | {
406 | UpdateMissingFieldsForEntity(entity, schema);
407 | }
408 | }
409 | ///
410 | /// enforces missing fields for entity given schema
411 | ///
412 | /// entity to update
413 | /// schema to enforce
414 | private void UpdateMissingFieldsForEntity(GraphEntity entity, Schema schema)
415 | {
416 | var missingFields = schema.Properties.Where(prop => !entity.Properties.ContainsKey(prop.Name));
417 | foreach (var missingField in missingFields)
418 | {
419 | entity.Properties.Add(missingField.Name, "");
420 | }
421 | }
422 | ///
423 | /// Attempts to fill object from properties
424 | ///
425 | /// Type of the object to fill
426 | /// entity, in which property values are stored
427 | /// object that will be filled with property values
428 | public void FillObjectFromProperties(GraphEntity entity, T objectToFill)
429 | {
430 | var objectProperties = typeof(T).GetProperties();
431 | foreach (var property in objectProperties)
432 | {
433 |
434 | entity.Properties.TryGetValue(property.Name, out var propertyValue);
435 | if(typeConversionProvider.TryConvert(typeof(string), property.PropertyType, propertyValue, out var convertedValue))
436 | property.SetValue(objectToFill, convertedValue);
437 | }
438 | }
439 |
440 | ///
441 | /// Attempts to fill object from properties
442 | ///
443 | /// Type of the object to fill
444 | /// entity, in which property values are stored
445 | /// object that will be filled with property values
446 | public Dictionary GetPropertiesFromObject(T objectToExtractValuesFrom)
447 | {
448 | var objectProperties = typeof(T).GetProperties();
449 | var result = new Dictionary();
450 | foreach (var property in objectProperties)
451 | {
452 |
453 | result.Add(property.Name, property.GetValue(objectToExtractValuesFrom).ToString());
454 | }
455 |
456 | return result;
457 | }
458 | ///
459 | /// Creates a Relation
460 | ///
461 | /// Type of the inserted node
462 | /// object properties
463 | public void CreateNode(T value, params string[] additionalLabels)
464 | {
465 | var label = value.GetType().Name;
466 | if (!Schemas.Exists(s => s.Label == label))
467 | {
468 | CreateSchema(label,
469 | value.GetType().GetProperties().Select(prop => new Property()
470 | { FullTypeName = prop.PropertyType.FullName, Name = prop.Name }).ToList());
471 | }
472 | else
473 | {
474 | UpdateSchema(new Schema()
475 | {
476 | Label = label,
477 | Properties = value.GetType().GetProperties().Select(prop => new Property()
478 | { FullTypeName = prop.PropertyType.FullName, Name = prop.Name }).ToList()
479 | });
480 | }
481 | Debug.WriteLine("Node created");
482 | var labels = new HashSet(additionalLabels);
483 | labels.Add(label);
484 | CreateNode(GetPropertiesFromObject(value), labels);
485 | }
486 |
487 | ///
488 | /// Creates a Relation given an object.
489 | ///
490 | /// Type of the inserted node
491 | /// object from which property values will be extracted
492 | public void CreateRelation(Func, Node> sourceNode, Func, Node> targetNode, T properties, params string[] additionalLabels)
493 | {
494 | var label = properties.GetType().Name;
495 | if (!Schemas.Exists(s => s.Label == label))
496 | {
497 | CreateSchema(label,
498 | properties.GetType().GetProperties().Select(prop => new Property()
499 | { FullTypeName = prop.PropertyType.FullName, Name = prop.Name }).ToList());
500 | }
501 | else
502 | {
503 | UpdateSchema(new Schema()
504 | {
505 | Label = label,
506 | Properties = properties.GetType().GetProperties().Select(prop => new Property()
507 | { FullTypeName = prop.PropertyType.FullName, Name = prop.Name }).ToList()
508 | });
509 | }
510 |
511 | CreateRelation(label, sourceNode, targetNode, GetPropertiesFromObject(properties), new HashSet(additionalLabels));
512 | }
513 |
514 | public void Update(string hash, T objectValues)
515 | {
516 | var entity = this.Entities().FirstOrDefault(x => x.Hash == hash);
517 | var propertiesWithValues = GetPropertiesFromObject(objectValues);
518 | foreach (var propertyWithValue in propertiesWithValues)
519 | {
520 | if (entity.Properties.ContainsKey(propertyWithValue.Key))
521 | entity.Properties[propertyWithValue.Key] = propertyWithValue.Value;
522 | }
523 |
524 | Update(entity);
525 | }
526 |
527 | public int Delete(string hash)
528 | {
529 | var nodes = Nodes.RemoveWhere(n => n.Hash == hash);
530 | var relationsLinkedWithNodes = Relations.RemoveWhere(r => r.SourceHash == hash || r.TargetHash == hash);
531 | var relations = Relations.RemoveWhere(r => r.Hash == hash);
532 |
533 | if(realtime) SaveDatabase();
534 | return nodes + relationsLinkedWithNodes + relations;
535 | }
536 |
537 | }
538 | }
--------------------------------------------------------------------------------
/SliccDB/SliccDB.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | latest
6 | true
7 |
8 |
9 |
10 | SliccDB
11 | SLICC_128.png
12 | MIT
13 | SliccDB
14 | 0.1.1
15 | A simple, one file graph database!
16 | Piotr Mikstacki
17 | https://github.com/pmikstacki/SliccDB
18 | https://github.com/pmikstacki/SliccDB
19 | https://github.com/pmikstacki/SliccDB
20 | https://raw.githubusercontent.com/pmikstacki/SliccDB/master/README.md
21 | Graph, Database
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | PreserveNewest
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Always
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Piotr Mikstacki
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/screenshots/Explorer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmikstacki/SliccDB/f5c1e7c18b4f3ed8fa6a12bc167cec89c46d7540/screenshots/Explorer.png
--------------------------------------------------------------------------------