Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mark functions as immutable #3

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

cereum
Copy link
Contributor

@cereum cereum commented Sep 14, 2024

When using the output of squids.encode(...)in a GENERATED column, postgres was complaining that the functions needs to be immutable.

-- Example Table
CREATE TABLE public.order(
  id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  external_id text UNIQUE NOT NULL GENERATED ALWAYS AS (sqids.encode(ARRAY[id, 0], 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 5)) STORED,
  amount numeric NOT NULL,
  name text,
  created_at timestamp with time zone DEFAULT timezone('utc', now()) NOT NULL,
  updated_at timestamp with time zone DEFAULT timezone('utc', now()) NOT NULL
);

From the postgres documentation:

An IMMUTABLE function cannot modify the database and is guaranteed to return the same results given the same arguments forever. This category allows the optimizer to pre-evaluate the function when a query calls it with constant arguments. For example, a query like SELECT ... WHERE x = 2 + 2 can be simplified on sight to SELECT ... WHERE x = 4, because the function underlying the integer addition operator is marked IMMUTABLE.

TL;DR Functions marked as IMMUTABLE always return the same output for the same input and do not depend on any external state. Non-IMMUTABLE functions may depend on database state or have side effects.

Accordingly, I've marked the following functions as follows:

Function IMMUTABLE
sqids.shuffle Yes
sqids.checkAlphabet Yes
sqids.toId Yes
sqids.toNumber Yes
sqids.encodeNumbers Yes
sqids.decode Yes
sqids.encode (both versions) Yes
sqids.isBlockedId No
sqids.defaultBlocklist No

The tests included with the repo all pass locally on my machine with these changes.

@4kimov 4kimov requested a review from kryptus36 September 15, 2024 03:21
@cereum cereum force-pushed the mark-fns-as-immutable branch 2 times, most recently from 421196a to 222675a Compare September 15, 2024 18:38
@cereum
Copy link
Contributor Author

cereum commented Sep 15, 2024

Only two functions fail the sqids.blocklist_test() when marked immutable:

sqids.defaultBlocklist()
sqids.isBlockedId(id TEXT)

All other immutable functions pass the tests.

@cereum cereum changed the title mark sqids.encode as immutable mark functions as immutable Sep 15, 2024
@cereum cereum force-pushed the mark-fns-as-immutable branch from 6e0b27e to 9acb313 Compare September 17, 2024 21:01
@cereum
Copy link
Contributor Author

cereum commented Sep 28, 2024

@kryptus36 @4kimov any thing you guys want me to change, or fundamental deficits in this PR?

@kevinmarsh
Copy link

Thanks @cereum I've just started looking into whether to adopt this library into one of my projects and noticed immutability was lacking, so appreciate this PR.


Technically if sqids.isBlockedId is not immutable, then I think something like sqids.encodeNumbers/sqids.encode (which calls isBlockedId) must also not be immutable. However that'd kind of rely on someone's workflow being something like pseudocode:

SELECT sqids.encode(ARRAY[1], 'abc'); -- Returns 'aa'
INSERT INTO sqids.blocklist (str) VALUES ('aa'), ('bb');
SELECT sqids.encode(ARRAY[1], 'abc');  -- Returns 'cc'

So pragmatically speaking I'd prefer to leave things like sqids.encode as IMMUTABLE as you've done, since in a normal workflow they wouldn't be changing? Alternatively maybe the library should be accurate in its IMMUTABLE descriptions and if end users need immutable versions they can create wrapper functions in their own codebase marked as IMMUTABLE

@cereum
Copy link
Contributor Author

cereum commented Oct 3, 2024

Thanks @cereum I've just started looking into whether to adopt this library into one of my projects and noticed immutability was lacking, so appreciate this PR.

Technically if sqids.isBlockedId is not immutable, then I think something like sqids.encodeNumbers/sqids.encode (which calls isBlockedId) must also not be immutable. However that'd kind of rely on someone's workflow being something like pseudocode:

SELECT sqids.encode(ARRAY[1], 'abc'); -- Returns 'aa'
INSERT INTO sqids.blocklist (str) VALUES ('aa'), ('bb');
SELECT sqids.encode(ARRAY[1], 'abc');  -- Returns 'cc'

So pragmatically speaking I'd prefer to leave things like sqids.encode as IMMUTABLE as you've done, since in a normal workflow they wouldn't be changing? Alternatively maybe the library should be accurate in its IMMUTABLE descriptions and if end users need immutable versions they can create wrapper functions in their own codebase marked as IMMUTABLE

These are all fair. One thing that I can think of is having a "static blocklist" version of encode in the library.
Using these ids as a generated column (at least in my use case) prevents me from having to use a transaction when interacting with my db, saves a network call, and prevents conflicts.

Like you said I think that the venn diagram of people who need the blocklist to be dynamic is small.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants