rel icon indicating copy to clipboard operation
rel copied to clipboard

About Relations (associations, association_meta)

Open IngwiePhoenix opened this issue 10 months ago • 1 comments

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

IngwiePhoenix avatar Feb 16 '25 01:02 IngwiePhoenix

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)

Fs02 avatar Feb 19 '25 13:02 Fs02