About Relations (associations, association_meta)
Hey there!
About a weekend and some later, and I am much more confident in my driver - I feel like it's maturing, and so is the accompaning REL implementation. So, I have started to write some playground-style tests to see how far it works ... or does not.
Right now, I am trying to get related record loading to work; so let me give you the TL;DR:
DEFINE TABLE users SCHEMAFULl;
DEFINE FIELD name ON users TYPE string;
DEFINE FIELD tags ON users TYPE array<string>;
DEFINE FIELD photos ON users TYPE record<photos>;
DEFINE TABLE photos SCHEMAFULL;
DEFINE FIELD title ON photos TYPE string;
DEFINE FIELD file ON photos TYPE bytes;
That's a very basic 1:n relation. To query all users, with their photos:
SELECT * FROM users FETCH photos;
The resulting JSON will also contain the photos - as already attached objects.
{
"id": "user:test",
"name": "Test User",
"tags": ["dev", "nerd"],
"photos": [
{
"id": "photos:abcdef",
"title": "A selfie",
"data": [...]
},
{
"id": "photos:zyxvut",
"title": "Another selfie",
"data": [...]
}
]
}
This obviously isn't how the big classics, MySQL and PostgresQL behave.
The way the driver handles it, is that it returns both tags and photos as []byte.
This is demonstrated here. Unfortunately, inserting data does not work: Found NONE for field risks_rel, with record processes:Lohnabrechnung2, but expected a record<risks>. That's a SurrealDB error, and here is the query that produced that:
surrealdb:driver:connection:execWithArgs INSERT INTO processes (title, description, responsible, risks, storage, id, created_at, updated_at, legal_basis, tasks, affected_data) VALUES ($_1, $_2, $_3, $_4, $_5, $_6, $_7, $_8, $_9, $_10, $_11) ON DUPLICATE KEY UPDATE storage = $_12, title = $_13, description = $_14, responsible = $_15, risks = $_16, tasks = $_17, affected_data = $_18, id = $_19, created_at = $_20, updated_at = $_21, legal_basis = $_22;
surrealdb:driver:connection:execWithArgs map[string]interface {}{
surrealdb:driver:connection:execWithArgs "_1": "Lohnabrechnung",
surrealdb:driver:connection:execWithArgs "_10": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Einholen der Bankdaten"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_11": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Kontoinformationen"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_12": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"NAS", "Aktenschrank"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_13": "Lohnabrechnung",
surrealdb:driver:connection:execWithArgs "_14": "Beispiel der Lohnabrechnung",
surrealdb:driver:connection:execWithArgs "_15": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Alex", "Daniel"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_16": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Datendiebstahl", "Ausnutzung"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_17": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Einholen der Bankdaten"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_18": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Kontoinformationen"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_19": "Lohnabrechnung2",
surrealdb:driver:connection:execWithArgs "_2": "Beispiel der Lohnabrechnung",
surrealdb:driver:connection:execWithArgs "_20": "d'2025-02-16T02:10:20+01:00'",
surrealdb:driver:connection:execWithArgs "_21": "d'2025-02-16T02:10:20+01:00'",
surrealdb:driver:connection:execWithArgs "_22": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"GOB", "Wirtschaft"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_3": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Alex", "Daniel"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_4": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"Datendiebstahl", "Ausnutzung"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_5": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"NAS", "Aktenschrank"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs "_6": "Lohnabrechnung2",
surrealdb:driver:connection:execWithArgs "_7": "d'2025-02-16T02:10:20+01:00'",
surrealdb:driver:connection:execWithArgs "_8": "d'2025-02-16T02:10:20+01:00'",
surrealdb:driver:connection:execWithArgs "_9": surrealtypes.ArrayOf[string]{
surrealdb:driver:connection:execWithArgs Values: {"GOB", "Wirtschaft"},
surrealdb:driver:connection:execWithArgs },
surrealdb:driver:connection:execWithArgs }
(I have some actually usable debug logging now...it helps a lot.)
The actual struct being passed is:
lohnabrechnung := Process{
ID: "Lohnabrechnung2",
Title: "Lohnabrechnung",
Description: "Beispiel der Lohnabrechnung",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Responsible: st.ArrayOf[string]{Values: []string{"Alex", "Daniel"}},
LegalBasis: st.ArrayOf[string]{Values: []string{"GOB", "Wirtschaft"}},
Risks: st.ArrayOf[string]{Values: []string{"Datendiebstahl", "Ausnutzung"}},
Storage: st.ArrayOf[string]{Values: []string{"NAS", "Aktenschrank"}},
Tasks: st.ArrayOf[string]{Values: []string{"Einholen der Bankdaten"}},
AffectedData: st.ArrayOf[string]{Values: []string{"Kontoinformationen"}},
RelatedRisks: []Risk{ // <--- This does not get sent!
{Title: "Beispiel", Description: "Beschreibungszeug"},
{Title: "Beispiel 2", Description: "Beschreibungszeug 2"},
},
}
Is there any interface I can implement in my adapter to work with/around that?
Thank you and kind regards,
Ingwie
Hi,
Is sending the field on the same function call is what you need?
Currently REL will include the field based on whether it's an association field or not. A dumb fallback now is to check if there's id field inside the record.
https://github.com/go-rel/rel/blob/master/document_meta.go#L264-L269
When it's detected as association, that field will be send as separate call.
One quick way I can think of is to add a new structag, that can be used to instruct rel to not treat the field as association 🤔
(Similar to structag used by embedded field)