Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pPPNG-0000Qc-PU for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Feb 2023 14:55:10 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1pPPNE-0000rc-EP for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Feb 2023 14:55:08 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pPPNE-0000rT-3c for pgsql-hackers@lists.postgresql.org; Tue, 07 Feb 2023 14:55:08 +0000 Received: from mail-qv1-xf2e.google.com ([2607:f8b0:4864:20::f2e]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1pPPNB-0003J3-DM for pgsql-hackers@lists.postgresql.org; Tue, 07 Feb 2023 14:55:06 +0000 Received: by mail-qv1-xf2e.google.com with SMTP id c5so3592333qvk.13 for ; Tue, 07 Feb 2023 06:55:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=aiven.io; s=google; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:from:to:cc:subject:date:message-id:reply-to; bh=imXujsr+9g0JZCrJeQoUA7Z+jQlfWKDLvntTfRqAcSU=; b=QiczaviF0Wlah3vfdOE3RdfJ97AY1tqK1j/jVKjRpi2hbUEl8HVpVDAkU4bxOP3mzy lW+P6ZkUfxuBwOVyXK85r9Ke7mQuOMl3t+GzLET/vqLJ0VeZW9XvjmJiBJZjd1YqPZJj 3UJm9/gqeb7AjYMXFc0H8eM7necYtSj4U7OXk= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=imXujsr+9g0JZCrJeQoUA7Z+jQlfWKDLvntTfRqAcSU=; b=xNFNlQojWdQ7HEeDXOP6foK4KD6tX5bEC8JTBWSwmMNsaT3jknL+wKQmOhHP0L7hnJ Ujzw8bKcq/bNdt4qsd5N/U0lDQ9IcSCg0RSE2OWh0fAeQHd0BTdIFxioGqOEM4xn6cRH 6l4Z7Df/t8vjP7Az6uzSfUN2OjKDcY2HU6A1EjQOWyOFvGL8gnAJXOR4Ntsetus0lm01 EWxGx8p84gtmqqLq7DZYkkXE+k85U3uxJ8ZWaLhU0JCDwz/Wohzvr3IKkTr4DEfTm0dL gPo6auIBqKQ4Pq4Iub7i0DpVWTsKSCK0/UAU9g0llEb/FmOieey5KRFJDNmq3mMXmSpj +3Ew== X-Gm-Message-State: AO0yUKU7k5o0kXVMH2vqw0gnYprgA3Ajvch/gUpduDun0H52NtCjnYNa RxP3Z63b8OY40TJVdSRWBcuj1FqZDnwH5E/2O//OlrsUdua2VOVKW/Is9Kxdu6WcPLw/cmwLZxf P0BWLcaRriF7d3yO/f5X8J8UqnDePo9/kmgowJSXSSBHVI10QWeBQgd5hDw6urt3aifWkhonFtJ j7DM2X2vatqCrrCR/0 X-Google-Smtp-Source: AK7set+ITEnXwTjiwWjDqUnZ7wCVb+3dJeKH1d2R1Z/ubuGChSSt+/t/rAsuGqJHyIU8nBhue7WXAg== X-Received: by 2002:a05:6214:628:b0:56b:ee5a:89ed with SMTP id a8-20020a056214062800b0056bee5a89edmr5269599qvx.23.1675781703520; Tue, 07 Feb 2023 06:55:03 -0800 (PST) Received: from aivenlaptop.localnet ([45.13.105.93]) by smtp.gmail.com with ESMTPSA id j6-20020a05620a410600b0070383f1b6f1sm9444066qko.31.2023.02.07.06.55.02 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 07 Feb 2023 06:55:03 -0800 (PST) From: Ronan Dunklau To: pgsql-hackers@lists.postgresql.org Subject: SQLFunctionCache and generic plans Date: Tue, 07 Feb 2023 15:55:34 +0100 Message-ID: <8216639.NyiUUSuA9g@aivenlaptop> MIME-Version: 1.0 Content-Transfer-Encoding: 7Bit Content-Type: text/plain; charset="us-ascii" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hello, It has been brought to my attention that SQL functions always use generic plans. Take this function for example: create or replace function test_plpgsql(p1 oid) returns text as $$ BEGIN RETURN (SELECT relname FROM pg_class WHERE oid = p1 OR p1 IS NULL LIMIT 1); END; $$ language plpgsql; As expected, the PlanCache takes care of generating parameter specific plans, and correctly prunes the redundant OR depending on wether we call the function with a NULL value or not: ro=# select test_plpgsql(NULL); LOG: duration: 0.030 ms plan: Query Text: (SELECT relname FROM pg_class WHERE oid = p1 OR p1 IS NULL LIMIT 1) Result (cost=0.04..0.05 rows=1 width=64) InitPlan 1 (returns $0) -> Limit (cost=0.00..0.04 rows=1 width=64) -> Seq Scan on pg_class (cost=0.00..18.12 rows=412 width=64) LOG: duration: 0.662 ms plan: Query Text: select test_plpgsql(NULL); Result (cost=0.00..0.26 rows=1 width=32) ro=# select test_plpgsql(1); LOG: duration: 0.075 ms plan: Query Text: (SELECT relname FROM pg_class WHERE oid = p1 OR p1 IS NULL LIMIT 1) Result (cost=8.29..8.30 rows=1 width=64) InitPlan 1 (returns $0) -> Limit (cost=0.27..8.29 rows=1 width=64) -> Index Scan using pg_class_oid_index on pg_class (cost=0.27..8.29 rows=1 width=64) Index Cond: (oid = '1'::oid) LOG: duration: 0.675 ms plan: Query Text: select test_plpgsql(1); Result (cost=0.00..0.26 rows=1 width=32) But writing the same function in SQL: create or replace function test_sql(p1 oid) returns text as $$ SELECT relname FROM pg_class WHERE oid = p1 OR p1 IS NULL LIMIT 1 $$ language sql; we end up with a generic plan: ro=# select test_sql(1); LOG: duration: 0.287 ms plan: Query Text: SELECT relname FROM pg_class WHERE oid = p1 OR p1 IS NULL LIMIT 1 Query Parameters: $1 = '1' Limit (cost=0.00..6.39 rows=1 width=32) -> Seq Scan on pg_class (cost=0.00..19.16 rows=3 width=32) Filter: ((oid = $1) OR ($1 IS NULL)) This is due to the fact that SQL functions are planned once for the whole query using a specific SQLFunctionCache instead of using the whole PlanCache machinery. The following comment can be found in functions.c, about the SQLFunctionCache: * Note that currently this has only the lifespan of the calling query. * Someday we should rewrite this code to use plancache.c to save parse/plan * results for longer than that. I would be interested in working on this, primarily to avoid this problem of having generic query plans for SQL functions but maybe having a longer lived cache as well would be nice to have. Is there any reason not too, or pitfalls we would like to avoid ? Best regards, -- Ronan Dunklau