Регулярное выражение заменяется только в том случае, если оно завернуто в определенную функцию

Я хочу создать функцию 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.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
0
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Здесь есть два шаблона, которые вы ищете: один заключен в функции идентификатора, а другой - только с предшествующим символом $, поэтому вы можете использовать шаблон чередования для поиска обоих из них, захватить имена переменных каждого, если any и замените совпадение тем, что захвачено.

Найти (с флагом без учета регистра):

identifier\(\$(\w+)\)|\$(\w+)

Заменить:

\1\2

Демо: https://regex101.com/r/2SriK9/3

Порядок этого имеет значение, хотя правильно? В противном случае он заменит их все, не найдя конкретных идентификаторов.

RuSXSnIpERXx 17.01.2023 05:35

Точно. Подшаблоны в шаблоне чередования работают в порядке появления.

blhsing 17.01.2023 05:35

Вместо использования регулярных выражений для выполнения сложных замен вы можете использовать библиотеку, такую ​​как 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())

Другие вопросы по теме