Разделить строку запроса базы данных с помощью регулярных выражений в ruby - PullRequest
1 голос
/ 07 июля 2019

У меня есть строка запроса, которую я хочу выделить

created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30' AND updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30' AND user_id = 5 AND status = 'closed'

Как это

created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30'

updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30'

user_id = 5

status = 'closed'

Это просто пример строки, я хочу динамически разделить строку запроса. Я знаю, что не могу просто разделить с AND из-за шаблона, как BETWEEN .. AND

Ответы [ 2 ]

1 голос
/ 07 июля 2019

На самом деле это не регулярное выражение, а более простой синтаксический анализатор.

  1. Это работает путем сопоставления регулярного выражения с начала строки до тех пор, пока оно не встретит пробел, за которым следует либо and, либо between, за которым следует символ пробела. Результат удаляется из where_cause и сохраняется в statement.
  2. Если начало строки теперь начинается с пробела, за которым следует between, за которым следует пробел. Он добавляется к statement и удаляется из where_cause с чем угодно, что позволяет 1 and. Сопоставление прекращается, если достигнут конец строки или обнаружен другой and.
  3. Если точка 2 не совпадает, проверьте, начинается ли строка с пробела, затем следует and, за которым следует пробел. Если это так, удалите это из where_cause.
  4. Наконец, добавьте statement в массив statements, если это не пустая строка.

Все сопоставления выполняются без учета регистра.

where_cause = "created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30' AND updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30' AND user_id = 5 AND status = 'closed'"

statements = []
until where_cause.empty?
  statement = where_cause.slice!(/\A.*?(?=[\s](and|between)[\s]|\z)/mi)

  if where_cause.match? /\A[\s]between[\s]/i
    between = /\A[\s]between[\s].*?[\s]and[\s].*?(?=[\s]and[\s]|\z)/mi
    statement << where_cause.slice!(between)
  elsif where_cause.match? /\A[\s]and[\s]/i
    where_cause.slice!(/\A[\s]and[\s]/i)
  end

  statements << statement unless statement.empty?
end

pp statements
# ["created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30'",
#  "updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30'",
#  "user_id = 5",
#  "status = 'closed'"]

Примечание: Ruby использует \A для сопоставления начала строки и \z для сопоставления конца строки вместо обычных ^ и $, которые соответствуют началу и окончание строки соответственно. См. документацию по привязке регулярного выражения .

Вы можете заменить каждый [\s] на \s, если хотите. Я добавил их, чтобы сделать регулярное выражение более читабельным.

Имейте в виду, что это решение не идеально, но может дать вам представление о том, как решить проблему. Причина, по которой я это говорю, заключается в том, что в нем не учитываются слова and / between в имени столбца или в строковом контексте.

Следующее, где причина:

where_cause = "name = 'Tarzan AND Jane'"

Будет выводить:

#=> ["name = 'Tarzan", "Jane'"]

Это решение также предполагает правильно структурированные SQL-запросы. Следующие запросы не приводят к тому, что вы думаете:

where_cause = "created_at = BETWEEN AND"
# TypeError: no implicit conversion of nil into String
# ^ does match /\A[\s]between[\s]/i, but not the #slice! argument 
where_cause = "id = BETWEEN 1 AND 2 BETWEEN 1 AND 3"
#=> ["id = BETWEEN 1 AND 2 BETWEEN 1", "3"]
1 голос
/ 07 июля 2019

Вы можете сделать это с помощью регулярных выражений, но вот парсер, который может работать для вашего случая использования. Конечно, его можно улучшить, но оно должно работать.

require 'time'

def parse(sql)
  arr = []
  split = sql.split(' ')
  date_counter = 0
  split.each_with_index do |s, i|
    date_counter = 2 if s == 'BETWEEN'
    time = Time.parse(s.strip) rescue nil
    date_counter -= 1 if time
    arr << i+1 if date_counter == 1
  end
  arr.select(&:even?).each do |index|
    split.insert(index + 2, 'SPLIT_ME')
  end
  split = split.join(' ').split('SPLIT_ME').map{|l| l.strip.gsub(/(AND)$/, '')}
  split.map do |line|
    line[/^AND/] ? line.split('AND') : line
  end.flatten.select{|l| !l.empty?}.map(&:strip)
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...