sqlc icon indicating copy to clipboard operation
sqlc copied to clipboard

support SET variable in queries

Open wasaga opened this issue 5 years ago • 7 comments

while I can put SET ROLE in the queries, I cannot do SET variable TO value before queries, which is useful if I have row based security policies that utilize those variables.

wasaga avatar Sep 11 '20 20:09 wasaga

@wasaga I haven't used SET variable TO value before, can you provide a bigger example of how it works in practice and how you're using it currently?

kyleconroy avatar Sep 12 '20 23:09 kyleconroy

Copied from Omesh on SO

User-defined variables (prefixed with @):

You can access any user-defined variable without declaring it or initializing it. If you refer to a variable that has not been initialized, it has a value of NULL and a type of string.

SELECT @var_any_var_name

You can initialize a variable using SET or SELECT statement:

SET @start = 1, @finish = 10;    
#OR
SELECT @start := 1, @finish := 10;
SELECT * FROM places WHERE place BETWEEN @start AND @finish;

User variables can be assigned a value from a limited set of data types: integer, decimal, floating-point, binary or nonbinary string, or NULL value.

User-defined variables are session-specific. That is, a user variable defined by one client cannot be seen or used by other clients.

They can be used in SELECT queries using Advanced MySQL user variable techniques.

torrayne avatar Sep 16 '20 16:09 torrayne

my usecase is postgres row-level security, so that every query is executing in the context of currently set database role, authenticated user ID and some other parameters.

I ended up writing a wrapper function that opens a transaction, sets those parameters just for the transaction scope then runs a query.

func Run(ctx context.Context, db *sql.DB, readOnly ReadOnly, uid string, auth AuthProvider, role DBRole, fn func(context.Context, *Queries) error) (finalErr error) {
	conn, err := db.Conn(ctx)
	if err != nil {
		return err
	}
	defer func() {
		finalErr = multierror.Append(finalErr, conn.Close()).ErrorOrNil()
	}()

	tx, err := conn.BeginTx(ctx, &sql.TxOptions{ReadOnly: bool(readOnly)})
	if err != nil {
		return err
	}

	_, err = tx.ExecContext(ctx,
		`SELECT set_config('role', $1, true), set_config('request.uid', $2, true), set_config('request.auth', $3, true)`,
		string(role), uid, string(auth))
	if err != nil {
		return multierror.Append(errors.Wrap(err, "set security context"), tx.Rollback())
	}

	if err = fn(ctx, New(tx)); err != nil {
		return multierror.Append(err, tx.Rollback())
	}

	return tx.Commit()
}

wasaga avatar Sep 17 '20 03:09 wasaga

I believe the above snippet may be generalized and be part of sqlc to i.e. validate and safely expose JWT session parameters to the queries - i.e. check out http://postgrest.org/en/v5.0/auth.html

Postgres doesn't have built-in or good 3rd party JWT libraries, thus doing it in Go and propagate down to database engine probably makes a lot of sense to promote best practices securing the data access, and I believe the concept is pretty much same in other database engines.

wasaga avatar Sep 17 '20 03:09 wasaga

Useful to store actions history as well

-- name: DeleteUser :exec
SET app.current_user = $1;
DELETE FROM "user"
 WHERE id = $2;

zs-dima avatar Sep 30 '23 10:09 zs-dima

Hello, any update about this issue? I would need to use this type of query.

almottier avatar Jul 17 '24 12:07 almottier

It would be especially usefull to integrate with Supabase

masar3141 avatar Oct 07 '24 16:10 masar3141

Hello, any update about this issue?

vutranandpad avatar Mar 17 '25 09:03 vutranandpad