Я хочу создать функцию python, которая займет длинный сценарий SQL, который мне нужно создать таблицу и поместить переменные сеанса в сценарий, чтобы его можно было использовать в качестве представления в Snowflake. Например,
SET TABLE_NAME = MY_TABLE_NAME;
CREATE OR REPLACE VIEW MY_VIEW AS (
SELECT * FROM IDENTIFIER($TABLE_NAME)
)
С помощью скрипта Python предыдущий блок становится
CREATE OR REPLACE VIEW MY_VIEW AS(
SELECT * FROM MY_TABLE
)
Однако во время тестирования я понял, что если имя переменной находится внутри другой функции, последняя скобка захватывается и удаляется. Есть ли способ заменить строку значением переменной, только если она заключена в функцию идентификатора?
Я хотел бы этот код:
IDENTIFIER($VAR_NAME)
identifier($VAR_NAME)
SELECT * FROM $VAR_NAME
DATEADD('DAY',+1,$VAR_NAME)
Становиться:
VAR_NAME
VAR_NAME
SELECT * FROM VAR_NAME
DATEADD('DAY',+1,VAR_NAME)
Это то, что я пробовал до сих пор. https://regex101.com/r/2SriK9/2 Спасибо.
P.S. В последнем примере, если бы var_name была функцией, она должна была бы иметь функцию, а затем закрываться закрывающей скобкой: DATEADD('DAY',+1,MY_FUNC()) ',+1,MY_FUNC()] без закрывающей скобки в функции dateadd.
Здесь есть два шаблона, которые вы ищете: один заключен в функции идентификатора, а другой - только с предшествующим символом $
, поэтому вы можете использовать шаблон чередования для поиска обоих из них, захватить имена переменных каждого, если any и замените совпадение тем, что захвачено.
Найти (с флагом без учета регистра):
identifier\(\$(\w+)\)|\$(\w+)
Заменить:
\1\2
Демо: https://regex101.com/r/2SriK9/3
Точно. Подшаблоны в шаблоне чередования работают в порядке появления.
Вместо использования регулярных выражений для выполнения сложных замен вы можете использовать библиотеку, такую как sqlglot, для анализа запроса в AST, который затем можно обновить для получения желаемого запроса:
import sqlglot
def run_updates(ast, scope):
if isinstance(ast, list):
#remove any `SET` queries:
return [run_updates(i, scope) for i in ast if \
not isinstance(i, sqlglot.expressions.Command) or str(i.this).lower() != 'set']
if isinstance(ast, sqlglot.expressions.Create):
if isinstance(ast.this, (sqlglot.expressions.Func, sqlglot.expressions.UserDefinedFunction)):
#node is a function, save the function name
scope['functions'].append(ast.this.this.this)
if isinstance(ast, sqlglot.expressions.Anonymous):
if ast.this.lower() == 'identifier':
#remove `IDENTIFER($VALUE)` expressions
return sqlglot.expressions.Identifier(this = ast.args['expressions'][0].this.this[1:], quoted=False)
if isinstance(ast, sqlglot.expressions.Identifier):
if isinstance(ast.this, str) and ast.this[0] == '$':
#replace general `$session_variable` expressions
if ast.this[1:] in scope['functions']:
#if the session variable is function, update accordingly
return sqlglot.expressions.Anonymous(this = ast.this[1:])
return sqlglot.expressions.Identifier(this = ast.this[1:], quoted=False)
if hasattr(ast, 'args'):
ast.args = {a:run_updates(b, scope) for a, b in ast.args.items()}
return ast
def replace_identifiers(query):
ast, scope = sqlglot.parse(query), {'variables':{}, 'functions':[]}
return ';\n'.join(i.sql() for i in run_updates(ast, scope))
Тесты:
#replacing IDENFITIER
s = '''
SET TABLE_NAME = MY_TABLE_NAME;
CREATE OR REPLACE VIEW MY_VIEW AS (
SELECT * FROM IDENTIFIER($TABLE_NAME)
)
'''
print(replace_identifiers(s))
#replacing IDENTIFIER and session variables
s1 = '''
IDENTIFIER($VAR_NAME);
identifier($VAR_NAME);
SELECT * FROM $VAR_NAME;
DATEADD('DAY',1,$VAR_NAME);
'''
print(replace_identifiers(s1))
#replacing session variable as a function call, since the test name is declared as a function in a prior line
s2 = '''
CREATE FUNCTION VAR_NAME() RETURNS TIMESTAMP AS 'SELECT now()';
DATEADD('DAY',1,$VAR_NAME);
'''
print(replace_identifiers(s2))
Выход:
CREATE OR REPLACE VIEW MY_VIEW AS (SELECT * FROM TABLE_NAME)
VAR_NAME;
VAR_NAME;
SELECT * FROM VAR_NAME;
DATEADD('DAY', 1, VAR_NAME)
CREATE FUNCTION VAR_NAME() RETURNS TIMESTAMP AS 'SELECT now()';
DATEADD('DAY', 1, VAR_NAME())
Порядок этого имеет значение, хотя правильно? В противном случае он заменит их все, не найдя конкретных идентификаторов.