@@ -37,13 +37,6 @@ class Postgres extends SQL
3737 */
3838 protected const PERMISSIONS_FUNCTION = '_permissions_any ' ;
3939
40- /**
41- * Schemas known to have the permissions function, per connection.
42- *
43- * @var \WeakMap<object, array<string, bool>>|null
44- */
45- protected static ?\WeakMap $ permissionsFunctions = null ;
46-
4740 /**
4841 * @inheritDoc
4942 */
@@ -130,6 +123,9 @@ public function create(string $name): bool
130123 $ name = $ this ->filter ($ name );
131124
132125 if ($ this ->exists ($ name )) {
126+ // Schemas created before the function existed get it here.
127+ $ this ->createPermissionsFunction ($ name );
128+
133129 return true ;
134130 }
135131
@@ -154,7 +150,7 @@ public function create(string $name): bool
154150 " ;
155151 $ this ->getPDO ()->prepare ($ collation )->execute ();
156152
157- $ this ->ensurePermissionsFunction ($ name );
153+ $ this ->createPermissionsFunction ($ name );
158154
159155 return $ dbCreation ;
160156 }
@@ -174,10 +170,6 @@ public function delete(string $name): bool
174170 $ sql = "DROP SCHEMA IF EXISTS \"{$ name }\" CASCADE " ;
175171 $ sql = $ this ->trigger (Database::EVENT_DATABASE_DELETE , $ sql );
176172
177- if (self ::$ permissionsFunctions !== null ) {
178- unset(self ::$ permissionsFunctions [$ this ->getPDO ()]);
179- }
180-
181173 return $ this ->getPDO ()->prepare ($ sql )->execute ();
182174 }
183175
@@ -1861,75 +1853,34 @@ protected function getSQLPermissionsCondition(
18611853 // the inlined function because PDO reads a lone ? as a positional placeholder, and
18621854 // doubling it to escape breaks once a named placeholder is repeated, which the cursor
18631855 // conditions do. jsonb_exists_any would avoid both but is not indexable.
1864- if ($ this ->ensurePermissionsFunction ($ this ->getDatabase ())) {
1865- $ permissions = \array_map (
1866- fn ($ role ) => $ this ->getPDO ()->quote ("{$ type }( \"{$ role }\") " ),
1867- $ roles
1868- );
1869-
1870- return "{$ this ->getSQLSchema ()}{$ this ->quote (self ::PERMISSIONS_FUNCTION )}( {$ column }, ARRAY[ " . \implode (', ' , $ permissions ) . ']::text[]) ' ;
1871- }
1872-
18731856 $ permissions = \array_map (
1874- fn ($ role ) => "{ $ column } @> { $ this ->getPDO ()->quote (\json_encode ([ "{$ type }( \"{$ role }\") " ]))} ::jsonb " ,
1857+ fn ($ role ) => $ this ->getPDO ()->quote ("{$ type }( \"{$ role }\") " ) ,
18751858 $ roles
18761859 );
18771860
1878- return ' ( ' . \implode (' OR ' , $ permissions ) . ') ' ;
1861+ return "{ $ this -> getSQLSchema ()}{ $ this -> quote ( self :: PERMISSIONS_FUNCTION )} ( { $ column } , ARRAY[ " . \implode (', ' , $ permissions ) . ']::text[] ) ' ;
18791862 }
18801863
18811864 /**
1882- * Make sure the schema has the function permission checks are written against.
1883- *
1884- * Databases created before it existed get it on first use. Where it can't be created
1885- * (inside a transaction that may yet roll back, or without the rights to), the caller
1886- * falls back to one containment check per role.
1865+ * Create the function permission checks are written against, unless the schema has it.
18871866 *
18881867 * @param string $schema
1889- * @return bool
1868+ * @return void
18901869 */
1891- protected function ensurePermissionsFunction (string $ schema ): bool
1870+ protected function createPermissionsFunction (string $ schema ): void
18921871 {
1893- $ pdo = $ this ->getPDO ();
1894- $ schema = $ this ->filter ($ schema );
1895-
1896- $ known = self ::$ permissionsFunctions ??= new \WeakMap ();
1897-
1898- if (isset ($ known [$ pdo ][$ schema ])) {
1899- return true ;
1900- }
1901-
19021872 $ function = "\"{$ schema }\". \"" . self ::PERMISSIONS_FUNCTION . '" ' ;
19031873
1904- try {
1905- $ stmt = $ pdo ->prepare ('SELECT to_regprocedure(:function) IS NOT NULL ' );
1906- $ stmt ->bindValue (':function ' , "{$ function }(jsonb, text[]) " );
1907- $ stmt ->execute ();
1908- $ exists = (bool ) $ stmt ->fetchColumn ();
1909-
1910- if (!$ exists ) {
1911- if ($ pdo ->inTransaction ()) {
1912- return false ;
1913- }
1914-
1915- // Run through exec, which does not look for placeholders. A concurrent
1916- // creation of the same function is not an error.
1917- $ pdo ->exec ("
1918- DO \$\$ BEGIN
1919- CREATE FUNCTION {$ function }(jsonb, text[]) RETURNS boolean
1920- LANGUAGE sql IMMUTABLE PARALLEL SAFE
1921- AS 'SELECT \$1 OPERATOR(pg_catalog.?|) \$2';
1922- EXCEPTION WHEN duplicate_function OR unique_violation THEN NULL;
1923- END \$\$
1924- " );
1925- }
1926- } catch (PDOException ) {
1927- return false ;
1928- }
1929-
1930- $ known [$ pdo ] = [...($ known [$ pdo ] ?? []), $ schema => true ];
1931-
1932- return true ;
1874+ // Run through exec, which does not look for placeholders. A concurrent creation of
1875+ // the same function is not an error.
1876+ $ this ->getPDO ()->exec ("
1877+ DO \$\$ BEGIN
1878+ CREATE FUNCTION {$ function }(jsonb, text[]) RETURNS boolean
1879+ LANGUAGE sql IMMUTABLE PARALLEL SAFE
1880+ AS 'SELECT \$1 OPERATOR(pg_catalog.?|) \$2';
1881+ EXCEPTION WHEN duplicate_function OR unique_violation THEN NULL;
1882+ END \$\$
1883+ " );
19331884 }
19341885
19351886 /**
0 commit comments