Движок SQL только для SELECT statment - рукописный парсер на c ++ - PullRequest
1 голос
/ 11 октября 2010

Я новичок в компиляторах, но у меня есть движок SQL проекта - только для оператора select. Для этого я должен использовать только рукописный парсер и движок. Я изучил образцы LL (k) грамматики и техники рекурсивного спуска (предложенный stackoverflow для написания парсера вручную). Но ни в одном из примеров не было найдено способа построить дерево разбора из функций.
Может кто-нибудь из вас подскажет, как сделать весь процесс компиляции шаг за шагом, просто взяв " Выберите columnname1, columnname2 из таблицы " пример. И еще одна вещь - библиотеки наддува также не допускаются. Данные в памяти. Я использовал структуры для хранения данных. Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 11 октября 2010

Вы также можете найти грамматики BNF для SQL 92, 99 и 2003 онлайн, которые могут пригодиться.

Это полные грамматики, но не должно быть трудно выделить только ветвь SELECT.

Кроме того, вы можете изучить уже имеющиеся грамматики на http://www.antlr.org/grammar/list;, там есть несколько разновидностей SQL.

0 голосов
/ 11 октября 2010

Я бы сказал, что проще всего разобраться с этим, как если бы компилятор:

  • Tokenization
  • Парсинг (создание AST)

Токенизация - это идентификация «слов», для вашего примера это дает:

"Select", "columnname1", ",", "columnanme2", "from", "table"

Парсинг - это интерпретация этого списка токенов в абстрактном синтаксическом дереве.Учитывая ваши требования:

  • Первый токен должен быть "select" (без учета регистра)
  • За ним может следовать либо "*", либо список имен столбцов через запятую
  • "from" (без учета регистра) отмечает конец списка
  • за ним следует имя таблицы

Я собираюсь обрисовать это в Python

class Select:
  def __init__(self):
    self.columnList = None
    self.tableName = None

def Tokenize(statement):
  result = []
  for word in statement.split(" "):
    sub = word.split(",")             # the comma may not be separated by space
    for i in range(len(sub)-1):
      result.append(sub[i].lower())   # case insensitive
      result.append(",")
    result.append(sub[-1])
  return result

def CreateSelect(tokens):
  if len(tokens) == 0: raise NoToken()
  if tokens[0] != "select": raise NotASelectStatement()

  select = Select()

  i = 1
  while tokens[i] != "from":
    if tokens[i] == "*":
      select.columnList == ALL
      break
    else:
      select.columnList.append(tokens[i])
      i = i + 1
      if tokens[i] != ",": raise ExpectedComma(i)
      i = i + 1

   select.tableName = tokens[i+1]

Конечно, как вы понимаете, это ограниченный пример:

  • Он не учитывает несколько таблиц
  • Он не учитывает псевдонимы вaccount
  • Он не учитывает вложенные операторы select

Однако он работает довольно хорошо и довольно эффективно.Обычно это может быть еще более эффективным, если мы объединим фазы токенизации и синтаксического анализа, но в целом я обнаружил, что это только усложнило чтение кода.

Также обратите внимание, что для правильного сообщения об ошибках это будетбыло бы лучше:

  • не изменять токены (.lower()), а просто использовать сравнение без учета регистра
  • , чтобы присоединить правильное местоположение источника к каждому токену (строка / столбец) такчтобы можно было направить пользователя в нужное место

РЕДАКТИРОВАТЬ :

Как будет выглядеть AST с менее надуманным примером?

"select foo, bar from FooBar where category = 'example' and id < 500;"

Пойдем:

Select
|_ Columns (foo, bar)
|_ Table (FooBar)
\_ Where
   \_ And
      |_ Equal (category, 'example')
      \_ LessThan (id, 500)

Здесь у вас есть древовидная структура, которую вы хотите создать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...