Charger et exécuter des fonctions personnalisées dans une salle blanche¶
Vue d’ensemble¶
Vous pouvez charger des UDFs et des UDTFs Python personnalisées dans votre salle blanche et les exécuter à partir de vos modèles pour effectuer des actions de données complexes. Ces actions comprennent le machine learning ou la manipulation personnalisée des données dans une requête, dans le cadre d’une seule étape ou d’un flux à plusieurs étapes. Python est le seul langage de codage pris en charge pour les UDFs personnalisées.
Votre code importé peut importer et utiliser des paquets à partir d’un bundle de packages Python approuvés et l’APISnowpark.
Les fournisseurs et les consommateurs peuvent charger du code Python personnalisé dans une salle blanche, bien que le processus soit différent pour les fournisseurs et les consommateurs.
Cette rubrique montre comment charger et exécuter des UDFs et UDTFs Python personnalisés en tant que fournisseur ou consommateur.
Astuce
Pour obtenir des informations générales sur la manière de développer vos propres UDFs Python dans une salle blanche, consultez les rubriques suivantes :
Fonctionnement des UDFs sur Snowflake pour des informations générales sur la manière de créer des fonctions Python dans Snowflake.
Création de UDTFs dans Snowflake si vous souhaitez renvoyer des tables à partir de vos fonctions.
Création et chargement des modèles personnalisés dans une salle blanche. Les UDFs/UDTFs sont appelées à partir d’un modèle personnalisé.
Utilisation de Snowpark dans les salles blanches (si vous souhaitez appeler vos UDFs à partir de Snowpark).
Exécution du code chargé¶
Chaque bundle de code importé peut définir plusieurs fonctions qui s’appellent entre elles, mais un bundle n’expose qu’une seule fonction de gestionnaire. Cette fonction de gestionnaire peut être appelée par des modèles créés ou exécutés par quiconque utilise la salle blanche. Si le code crée des tables internes, ces tables sont accessibles comme décrit dans la section Conception de flux à plusieurs étapes.
Par exemple, si vous avez chargé une fonction nommée simple_add qui intègre deux paramètres numériques, vous pouvez l’appeler à partir d’un modèle comme illustré ici. La fonction est toujours référencée à l’aide de la cleanroom à champ d’application limité. Par exemple, un modèle pourrait appeler simple_add comme suit :
SELECT cleanroom.simple_add({{ price | sqlsafe | int }}, {{ tax | sqlsafe | int }}) ...
Astuce
Si le fournisseur veut exécuter le code ci-dessus, il doit donner un alias à toutes les colonnes SELECT qui utilisent une fonction agrégée ou personnalisée, car une table de résultats est générée en arrière-plan :
SELECT
cleanroom.simple_add(
{{ price | sqlsafe | int }}, {{ tax | sqlsafe | int }}
) AS TOTAL_ITEM_COST
...
Vous pouvez charger plusieurs fonctions dans un seul paquet, et les fonctions d’un même paquet peuvent s’appeler entre elles, mais les fonctions ne peuvent pas appeler de fonctions dans d’autres paquets. (Cependant, elles peuvent appeler les fonctions du gestionnaire). Par exemple :
Paquet 1 |
Paquet 2 |
|---|---|
|
|
Remarques :
Le code chargé de chaque côté (fournisseur et consommateur) peut être exécuté de chaque côté.
Un modèle peut appeler la fonction A ou la fonction B, mais pas A1, A2, B1, B2.
La fonction A peut appeler la fonction B, et inversement.
La fonction A ne peut pas appeler B1 ou B2 et la fonction B ne peut pas appeler A1 ou A2.
A1 peut appeler A2 et inversement. A1 et A2 peuvent appeler B. A1 et A2 ne peuvent pas appeler B1 ou B2.
B1 peut appeler B2 et inversement. B1 et B2 peuvent appeler A. B1 et B2 ne peuvent pas appeler A1 ou A2.
Mise à jour ou suppression des fonctions personnalisées¶
Vous pouvez charger ou remplacer une fonction ou un modèle existant que vous avez chargé, mais vous ne pouvez pas supprimer une fonction ou un modèle existant. La seule façon de « supprimer » une fonction est de créer une fonction fictive avec exactement le même nom et la même signature qui réussit toujours.
Le chargement d’une fonction avec exactement la même signature que celle que vous avez chargée précédemment remplacera la fonction existante. La signature est le nom de la fonction insensible à la casse du gestionnaire externe, et les types de données de tous les paramètres, dans le même ordre. Les noms des paramètres n’ont pas d’importance. Vous ne pouvez pas remplacer une fonction téléchargée par un autre compte.
Comme la signature doit correspondre lorsque vous mettez à jour une fonction, vous ne pouvez pas modifier la signature d’une fonction existante : si vous chargez la fonction foo(name VARIANT age INTEGER), puis chargez la fonction foo(name VARIANT age FLOAT), la deuxième fonction sera ajoutée à la salle blanche en plus de la première, car les types d’arguments diffèrent.
Code soumis par le fournisseur¶
Les fonctions soumises par le fournisseur peuvent être chargées sous forme de code en ligne ou à partir d’une zone de préparation Snowflake. Les deux techniques sont couvertes ici.
Votre code chargé peut importer et utiliser nativement des packages à partir d’un ensemble approuvé de packages Python. Si vous avez besoin d’un package non proposé par défaut, vous devez utiliser Snowpark Container Services dans une salle blanche pour héberger votre code.
Vous ne pouvez pas voir le code du fournisseur chargé, ni même votre propre code, alors veillez à inclure une copie de ce que vous chargez exactement dans une salle blanche.
Astuce
Après avoir mis à jour le code écrit par le fournisseur, vous devez mettre à jour la directive de version par défaut, puis appeler provider.create_or_update_cleanroom_listing pour propager les changements aux consommateurs. Si vous n’appelez pas provider.create_or_update_cleanroom_listing, votre version par défaut ne sera pas mise à jour pour les consommateurs qui utilisent actuellement la salle blanche.
Voici une vue de haut niveau de la manière dont un fournisseur ajoute du code à une salle blanche :
Le fournisseur crée et configure la salle blanche de manière habituelle.
Le fournisseur charge le code en appelant
provider.load_python_into_cleanroom. Vous pouvez charger votre code en ligne directement dans cette procédure ou charger un fichier de code dans une zone de préparation. Puis, indiquez l’emplacement de la zone de préparation dans cette procédure.Bien que votre code puisse inclure plusieurs fonctions, un seul gestionnaire est exposé pour chaque chargement. Pour exposer plusieurs fonctions pour les modèles, téléchargez chaque gestionnaire en appelant
provider.load_python_into_cleanroom.Après chaque chargement de code réussi, une nouvelle version corrective de la salle blanche est générée. Vous devez ensuite améliorer la version par défaut en appelant
provider.set_default_release_directiveavec le nouveau numéro de correctif. Si la salle blanche est exposée à l’extérieur, les contrôles de sécurité sont exécutés avant d’installer votre code et vous devez appelerprovider.view_cleanroom_scan_statuspour confirmer que les contrôles de sécurité ont réussi avant d’incrémenter la version par défaut.Si vous souhaitez charger plusieurs fonctions dans un seul correctif, vous pouvez charger en masse votre code. Cependant, les chargements en masse peuvent rendre plus difficile le débogage si le chargement présente un problème d’analyse de sécurité, parce que le fichier qui a causé le problème n’est pas signalé dans la réponse d’erreur.
Vous créez et chargez un modèle personnalisé qui appelle votre code. Le modèle doit appeler la fonction de gestionnaire à l’aide du champ d’application de la
cleanroom, qui est :cleanroom.my_function(...).Le consommateur exécute votre modèle de la même manière que tout autre modèle.
Astuce
Si le consommateur rencontre une erreur de montage lorsqu’il installe une salle blanche avec du code personnalisé, cela peut indiquer une erreur de syntaxe dans le code.
Vous trouverez des exemples de code démontrant ce flux dans la section d’exemple de code écrit par le fournisseur.
Notes importantes sur la gestion des versions¶
Chaque fois que le fournisseur charge une fonction, le nombre de correctifs augmente (avec une limite de 99 numéros de correctifs). Par conséquent, faites de votre mieux pour tester et déboguer rigoureusement votre code avant de l’ajouter à la salle blanche afin de réduire les mises à jour de versions au cours du développement.
Si vous mettez à jour un numéro de correctif, les clients qui utilisent l’UI des salles blanches peuvent devoir actualiser la page pour voir le changement. Les clients qui utilisent l’API devrait voir les changements immédiatement, mais cela peut prendre un certain temps en fonction des ressources disponibles. En savoir plus sur la gestion des versions des salles blanches.
Charger des fonctions en ligne écrites par le fournisseur¶
Vous pouvez charger le code en ligne dans le paramètre code de provider.load_python_into_cleanroom. Voici un exemple de chargement d’une fonction simple en ligne :
CALL samooha_by_snowflake_local_db.provider.load_python_into_cleanroom(
$cleanroom_name,
'simple_add', -- Name used to call the UDF from a template.
['first INTEGER', 'second INTEGER'], -- Arguments of the UDF, specified as '<variable_name> <SQL type>' pairs.
['numpy', 'pandas'], -- Packages imported by the UDF.
'INTEGER', -- SQL return type of UDF.
'add_two', -- Handler function in your code called when external name is called.
$$
import numpy as np # Not used, but you can load supported packages.
import pandas as pd
def add_two(first, second):
return first + second
$$
);
Le modèle d’appel appelle cleanroom.simple_add pour appeler cette fonction. Les exemples de fonctions écrites par le fournisseur montrent comment charger du code en ligne.
Chargement de fonctions écrites par un fournisseur à partir d’une zone de préparation¶
Vous pouvez charger des fichiers Python dans une zone de préparation de salle blanche et faire référence à la zone de préparation lorsque vous appelez provider.load_python_into_cleanroom. Le chargement de code à partir d’une zone de préparation vous permet de développer le code de votre système local dans un éditeur, d’éviter les erreurs copier/coller lorsque vous le chargez en ligne et d’avoir un meilleur contrôle des versions. Notez que vous pouvez charger plusieurs fichiers dans un seul appel de procédure, mais qu’une seule fonction de gestionnaire est exposée pour chaque chargement.
Le code est chargé depuis une zone de préparation dans la salle blanche lorsque vous appelez load_python_into_cleanroom. Les modifications ultérieures apportées au code sur la zone de préparation ne sont pas répercutées dans la salle blanche.
Pour charger votre UDF dans une zone de préparation :
Créez votre fichier .py et rendez-le disponible dans un emplacement où vous pouvez le charger dans une zone de préparation Snowsight.
Pour obtenir le nom de la zone de préparation de votre salle blanche, appelez
provider.get_stage_for_python_files($cleanroom_name). Cette zone de préparation est accessible par la salle blanche. Vous ne pouvez pas utiliser une zone de préparation arbitraire que vous créez.Chargez le fichier .py dans la zone de préparation de votre salle blanche. Il y a plusieurs façons de procéder, notamment en utilisant la CLI, Snowsight ou des pilotes spécifiques à la langue.
Appelez
provider.load_python_into_cleanroomavec l’emplacement de la zone de préparation, le gestionnaire, le nom externe, les arguments et le type de retour. Les modèles de votre salle blanche peuvent désormais appeler la fonction.
L’exemple de code suivant montre comment charger du code dans une salle blanche à partir d’une zone de préparation.
-- Save the following code as reverser.py:
--import numpy as np
--def main(some_string):
-- '''Return the reverse of a string plus a random number 1-10'''
-- return some_string[::-1] + str(np.random.randint(1,10))
-- Get the stage for your clean room.
CALL samooha_by_snowflake_local_db.provider.get_stage_for_python_files($cleanroom_name);
-- Save the file to the stage. Here is how to do it by using the Snowflake CLI
PUT file://~/reverser.py <STAGE_NAME> overwrite=True auto_compress=False;
-- Load the code from the stage into the clean room.
CALL samooha_by_snowflake_local_db.provider.load_python_into_cleanroom(
$cleanroom_name,
'reverse', -- Name used to call the function
['some_string STRING'], -- Arguments and SQL types
['numpy'], -- Any required packages
['/reverser.py'], -- Relative path to file on stage
'STRING', -- Return type
'reverser.main' -- <FILE_NAME>.<FUNCTION_NAME>
);
-- Uploading code, even from a stage, increases the patch number.
CALL samooha_by_snowflake_local_db.provider.set_default_release_directive(
$cleanroom_name, 'V1_0', <NEW_PATCH_NUMBER>);
-- Upload a template that calls the function.
CALL samooha_by_snowflake_local_db.provider.add_custom_sql_template(
$cleanroom_name,
$udf_template_name,
$$
SELECT
p.status,
cleanroom.reverse(p.status)
FROM SAMOOHA_SAMPLE_DATABASE.DEMO.CUSTOMERS AS p
LIMIT 100;
$$
);
-- Switch to the consumer account and run the template to see the results.
Les exemples de code écrit par le fournisseur illustrent le chargement de code à partir d’une zone de préparation.
Dépannage des erreurs de syntaxe ou des échecs d’analyse dans le code chargé¶
Si vous chargez une fonction qui échoue en raison d’une erreur de syntaxe, ou si une analyse de sécurité échoue, un correctif non publiable peut être généré. Par conséquent, vous devez tester soigneusement votre code avant de le charger pour vous assurer qu’il ne contient pas d’erreurs de syntaxe.
Vous pouvez voir la liste des paquets et leur état de révision en exécutant la commande SQL suivante, et en fournissant l’ID de la salle blanche à l’endroit indiqué :
SHOW VERSIONS IN APPLICATION PACKAGE samooha_cleanroom_cleanroom_id;
Analyses de sécurité¶
Une analyse de sécurité est exécutée après toute action qui génère une nouvelle version de correctif dans une salle blanche externe, par exemple lorsque le fournisseur télécharge Python dans la salle blanche. (Le code soumis par le consommateur, décrit sur cette page, ne déclenche pas d’analyse de sécurité). Les salles blanches internes n’effectuent pas d’analyses de sécurité, mais si vous transformez une salle blanche interne en salle blanche externe, une analyse de sécurité sera déclenchée pour ce correctif. Un correctif de salle blanche ne peut pas être publié en externe tant que le correctif n’a pas été analysé.
Snowflake Data Clean Rooms utilise le Framework d’analyse de sécurité des applications natives Snowflake. Suivez les meilleures pratiques de sécurité des applications natives pour éviter les erreurs d’analyse de sécurité.
Vous pouvez effectuer d’autres actions de création de correctifs avant que la dernière analyse de sécurité ne soit terminée. Cependant, vous devez attendre que provider.view_cleanroom_scan_status ait réussi avant de pouvoir mettre à jour la directive de version par défaut afin d’utiliser la dernière version de la salle blanche.
Chargement de plusieurs fonctions Python dans un seul correctif¶
Si vous souhaitez télécharger plusieurs paquets Python pouvant être appelés par des modèles dans votre salle blanche, vous pouvez appeler prepare_python_for_cleanroom plusieurs fois, puis appeler load_prepared_python_into_cleanroom une fois pour analyser, charger et générer un correctif unique pour votre salle blanche. L’exemple suivant illustre le chargement d’un UDF et d’un UDTF en utilisant le chargement en masse :
---- Add custom inline UDF ----
CALL samooha_by_snowflake_local_db.provider.prepare_python_for_cleanroom(
$cleanroom_name,
'get_next_status', -- Name of the UDF. Can be different from the handler.
['status VARCHAR'], -- Arguments of the UDF, specified as (variable name, SQL type).
['numpy'], -- Packages needed by UDF.
[], -- When providing the code inline, this is an empty array.
'VARCHAR', -- Return type of UDF.
'get_next_status', -- Handler.
$$
import numpy as np
def get_next_status(status):
"""Return the next higher status, or a random status
if no matching status found or at the top of the list."""
statuses = ['MEMBER', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND']
try:
return statuses[statuses.index(status.upper()) + 1]
except:
return 'NO MATCH'
$$
);
---- Add custom inline UDTF. ----
CALL samooha_by_snowflake_local_db.provider.prepare_python_for_cleanroom(
$cleanroom_name,
'get_info', -- Name of the UDTF. Can be different from the handler.
['hashed_email VARCHAR', 'days_active INT', 'status VARCHAR', 'income VARCHAR'], -- Name/Type arguments of the UDTF.
['numpy'], -- Packages used by UDTF.
[], -- When providing the code inline, this is an empty array.
'TABLE(hashed_email VARCHAR, months_active INT, level VARCHAR)', -- Return type of UDTF.
'GetSomeVals', -- Handler class name.
$$
class GetSomeVals:
def __init__(self):
self.month_days = 30
def process(self, hashed_email, days_active, status, income):
'''Change days into rough months, and also return whether we
think the user's membership status is lower, higher, or equal to
what is expected, based on their income.'''
months_active = days_active // self.month_days
brackets = ['0-50K', '50K-100K', '100K-250K', '250K+']
statuses = ['MEMBER', 'SILVER', 'GOLD', 'PLATINUM']
if(statuses.index(status) < brackets.index(income)):
level = 'low'
elif(statuses.index(status) > brackets.index(income)):
level = 'high'
else:
level = 'equal'
yield(hashed_email, months_active, level)
$$
);
-- Upload all stored procedures.
-- Note the new patch number returned by this procedure. Keep this number for later use.
CALL samooha_by_snowflake_local_db.provider.load_prepared_python_into_cleanroom($cleanroom_name);
-- Set the release directive specified by the last load_python_into_cleanroom call.
CALL samooha_by_snowflake_local_db.provider.set_default_release_directive($cleanroom_name, 'V1_0', <PATCH_NUMBER>);
Exemples de code écrits par le fournisseur¶
Les exemples suivants illustrent l’ajout de UDFs et de UDTFs écrites par le fournisseur dans une salle blanche.
Téléchargez les exemples suivants, puis chargez-les sous forme de fichiers de feuilles de calcul dans votre compte Snowflake. Vous avez besoin de comptes distincts pour le fournisseur et le consommateur, chacun avec l’API de salles blanches installée. Remplacez les informations comme indiqué dans les fichiers d’exemple.
:download :
Exemple de code fournisseur</samples/clean-rooms/provider-udf-p.sql>:download :
Exemple de code consommateur</samples/clean-rooms/provider-udf-c.sql>:download :
Chargement d’un fichier à partir d’une zone de préparation </samples/clean-rooms/udf_from_stage.ipynb>. Exécutez ce notebook après avoir exécuté l’exemple du fournisseur pour essayer de charger une UDF à partir d’une zone de préparation.:download :
Chargement de plusieurs fonctions Python dans un seul correctif. </samples/clean-rooms/upload-multiple-python-packages.sql>Il s’agit d’une salle blanche de test interne à compte unique ; vous pouvez utiliser le même compte pour le rôle de fournisseur et le rôle de consommateur.
Code soumis par le consommateur¶
Le code chargé par le consommateur est regroupé et importé avec un modèle personnalisé à l’aide du flux de chargement du modèle du consommateur. Le code chargé peut être appelé par n’importe quel modèle dans la salle blanche.
Pour charger du code en tant que consommateur, vous devez comprendre la syntaxe des modèles personnalisés.
Notez que tout code chargé par un consommateur peut être vu par le fournisseur lorsqu’il demande l’autorisation de l’importer. Le code du consommateur est également visible chaque fois qu’un fournisseur ou un consommateur examine le modèle.
Voici un aperçu des étapes à suivre pour charger un code consommateur personnalisé :
Le fournisseur crée la salle blanche de la manière standard, puis invite le consommateur.
Le consommateur installe et configure la salle blanche de la manière standard.
Le consommateur prépare un modèle qui appelle l’UDF ou l’UDTF dans l’espace de noms de la
cleanroom. Par exemple, pour appeler la fonctioncalculate_taxdéfinie par le consommateur, un modèle simple peut ressembler à l’extrait suivant :SELECT {{ cleanroom.calculate_tax(p.cost) }} AS Tax FROM my_db.my_sch.sales AS p;
Le consommateur prépare son code Python. Nous vous recommandons d’utiliser des guillemets doubles (
" ") plutôt que des guillemets simples (' ') dans votre code afin d’éviter tout échappement supplémentaire nécessaire ultérieurement. Votre code peut faire référence à ces bibliothèques Python prises en charge.Le consommateur transmet son code Python à
consumer.generate_python_request_template. La procédure renvoie le code Python sous forme de procédure stockée, avec un espace réservé pour le modèle JinjaSQL personnalisé. Il existe plusieurs chaînes de plusieurs lignes dans le modèle qui utilisent$$en tant que délimiteurs à plusieurs lignes.Le consommateur remplace l’espace réservé du modèle dans la sortie de
generate_python_request_templatepar son modèle JinjaSQL.Dans le modèle combiné, échappez les guillemets simples comme suit :
\'. En effet, les guillemets simples seront utilisés comme délimiteurs externes pour toute la chaîne de procédure multiligne quand vous le chargez dans la salle blanche. Voici un exemple de procédure stockée qui inclut le code Python du consommateur et le modèle personnalisé, avec l’échappement des caractères :BEGIN CREATE OR REPLACE FUNCTION CLEANROOM.custom_compare(min_status STRING, max_status STRING, this_status STRING) RETURNS boolean LANGUAGE PYTHON RUNTIME_VERSION = 3.10 PACKAGES = (\'numpy\') HANDLER = \'custom_compare\' AS $$ import numpy as np def custom_compare(min_status:str, max_status:str, this_status:str): statuses = [\'MEMBER\', \'SILVER\', \'GOLD\', \'PLATINUM\'] return ((statuses.index(this_status) >= statuses.index(min_status)) & (statuses.index(this_status) <= statuses.index(max_status))) $$; -- Custom template LET SQL_TEXT varchar := $$ SELECT c.status, c.hashed_email FROM IDENTIFIER( {{ my_table[0] }} ) as c WHERE cleanroom.custom_compare({{ min_status }}, {{ max_status }}, c.status); $$; LET RES resultset := (EXECUTE IMMEDIATE :SQL_TEXT); RETURN TABLE(RES); END;
Le consommateur appelle
consumer.create_template_requestavec le modèle combiné. Utilisez des guillemets simples (' ') au lieu de délimiteurs à double signe de dollar ($$...$$) autour du code que vous fournissez pour la procédure stockée dans l’argumenttemplate_definition. Par exemple :CALL samooha_by_snowflake_local_db.consumer.create_template_request( $cleanroom_name, $template_name, ' BEGIN -- First, define the Python UDF. CREATE OR REPLACE FUNCTION CLEANROOM.custom_compare(min_status STRING, max_status STRING, this_status STRING) RETURNS boolean LANGUAGE PYTHON RUNTIME_VERSION = 3.10 PACKAGES = (\'numpy\') HANDLER = \'custom_compare\' AS $$ import numpy as np def custom_compare(min_status:str, max_status:str, this_status:str): statuses = [\'MEMBER\', \'SILVER\', \'GOLD\', \'PLATINUM\'] return ((statuses.index(this_status) >= statuses.index(min_status)) & (statuses.index(this_status) <= statuses.index(max_status))) $$; -- Then define and execute the SQL query. LET SQL_TEXT varchar := $$ SELECT c.status, c.hashed_email FROM IDENTIFIER( {{ my_table[0] }} ) as c WHERE cleanroom.custom_compare({{ min_status }}, {{ max_status }}, c.status); $$; -- Execute the query and then return the result. LET RES resultset := (EXECUTE IMMEDIATE :SQL_TEXT); RETURN TABLE(RES); END; ');
Le consommateur et le fournisseur poursuivent avec le flux de modèles défini par le consommateur :
Le fournisseur voit la requête de modèle (
provider.list_pending_template_requests) et l’approuve ensuite en appelantapprove_template_request. Dans la requête, le fournisseur peut voir le modèle et le code en bundle.Le consommateur vérifie le statut de la requête (
consumer.list_template_requests), et lorsque le statut est APPROVED, il exécute le modèle (consumer.run_analysis).
Exemples de code écrit par les consommateurs¶
Les exemples suivants illustrent l’ajout d’UDFs écrites par le fournisseur dans une salle blanche.
Téléchargez les exemples suivants, puis chargez-les sous forme de fichiers de feuilles de calcul dans votre compte Snowflake. Vous avez besoin de comptes distincts pour le fournisseur et le consommateur, chacun avec l’API de salles blanches installée. Remplacez les informations comme indiqué dans les fichiers d’exemple :
:download :
Exemple de code fournisseur</samples/clean-rooms/consumer-udf-p.sql>:download :
Exemple de code consommateur</samples/clean-rooms/consumer-udf-c.sql>:download :
Code écrit par le consommateur et exécuté par le fournisseur : notebook du fournisseur</samples/clean-rooms/p-run-c-uploaded-code-p.sql>:download :
Code écrit par le consommateur et exécuté par le fournisseur : notebook du consommateur</samples/clean-rooms/p-run-c-uploaded-code-c.sql>