jet icon indicating copy to clipboard operation
jet copied to clipboard

Pointer versions of expression factories

Open anuraaga opened this issue 3 years ago • 7 comments

Is your feature request related to a problem? Please describe.

Currently, there are expression factories like String() to create literals for values, but they don't support pointers. This can be tedious, especially when copying data from one table to another, because model fields are by default pointers. Would it make sense to have pointer versions of the expression factories?

Describe the solution you'd like

For example,

func StringOrNULL(val *string) StringExpression {
	if val == nil {
		return StringExp(NULL)
	}
	return String(*val)
}


var friend := computeBestFriend()
Cat.UPDATE().
	SET(
		Cat.BestFriendName.SET(StringOrNULL(friend.Name))
	)

anuraaga avatar May 16 '22 08:05 anuraaga

Hi @anuraaga. When dealing with data retrieval, insert or update, recommended way is to use model types. Model types will add type and pointer safety to the query. And literal expressions(String, Float, ...) to use mainly for query conditions.

The same query using model type:

Cat.UPDATE(Cat.BestFriendName).
	MODEL(
             model.Cat{
                    BestFriendName: friend.Name
             }
	)

go-jet avatar May 16 '22 09:05 go-jet

Thanks @go-jet - that pattern seems reasonable to me.

anuraaga avatar May 17 '22 04:05 anuraaga

Oh actually I realized at least in my case I wouldn't be able to use MODEL. AFAIU, models can only be used with Go values, not expressions, so setting both that string field friend_name and e.g. created_at to CURRENT_TIMESTAMP() isn't possible. Perhaps this is too minor for adding pointer expression factories but what do you think?

anuraaga avatar May 17 '22 04:05 anuraaga

Yeah, it is a valid point. For created_at there are other alternatives. It is possible to set created_at to time.Now() in model type, or define a column to be DEFAULT CURRENT_TIMESTAMP or use trigger. There is a scenario in which update statement has to set some pointer value, plus some complex expression value (using different columns or value from select statement). It is not likely scenario, but if that's the case, developer can still write StringOrNULL, as you did. It is something to think about and maybe add in the future.

go-jet avatar May 17 '22 10:05 go-jet

It's impossible to use MODEL with ON_CONFLICT().DO_UPDATE(), so it's necessary to write some TypeOrNull helpers. While it's not that hard, it would be nice to have them in jet "out of box".

nkonin avatar Aug 12 '22 22:08 nkonin

Can you use EXCLUDED.ColumnName instead?

houtn11 avatar Aug 13 '22 15:08 houtn11

@milhousevanhouten11 that's another option, thanks!

nkonin avatar Aug 19 '22 15:08 nkonin

+1, really need for the ON_CONFLICT().DO_UPDATE()

Joker666 avatar Apr 13 '23 16:04 Joker666

@Joker666 What's your query? Can't you use EXCLUDED.ColumnName?

houtn11 avatar Apr 17 '23 11:04 houtn11

@houten11 I couldn't find any documentation on how to use the EXCLUDED.ColumnName. I can see from the doc there's a reference

Link.ID.SET(Link.EXCLUDED.ID),

https://github.com/go-jet/jet/wiki/INSERT#postgresql-sqlite-insert-with-on-conflict-update

I'm not sure how does it handle the pointer case, like the field can have value or can be null

Joker666 avatar Apr 24 '23 19:04 Joker666

Interestingly if I try to set nil, I find a runtime crash. @go-jet

Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
ON_CONFLICT(Link.ID).DO_UPDATE(
    SET(
        Link.URL.SET(nil),
    ),
)

Joker666 avatar Apr 25 '23 16:04 Joker666

EXCLUDED is a standard postgres keyword. More info here - https://www.postgresql.org/docs/15/sql-insert.html. Assuming we have this query:

Link.INSERT(Link.ID, Link.URL, Link.Name, Link.Description).
VALUES(100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
VALUES(100,  NULL, "PostgreSQL Tutorial", DEFAULT).     // first conflict
VALUES(100, "http://www.pg2.com", "PostgreSQL Tutorial", DEFAULT). // second conflict
ON_CONFLICT(Link.ID).DO_UPDATE(
    SET(
        Link.URL.SET(Link.EXCLUDED.URL),
    ),
)

For the first conflict Link.EXCLUDED.URL will be NULL and for the second conflict it will be "http://www.pg2.com". This means db will store "http://www.pg2.com" as the URL for ID 100.

If you still want to set Link.URL to NULL, you can use: Link.URL.SET(StringExp(NULL)), or if you want to set some non-NULL value: Link.URL.SET(String("www.someurl.com"))

houtn11 avatar Apr 25 '23 16:04 houtn11

Wow, I wonder why I didn't Google that first. Thanks a lot for explaining in detail.

🙏

Joker666 avatar May 01 '23 12:05 Joker666