Статьи

VoltDB — это взбалтывающая урна Groovy Funk

Эта статья была первоначально написана Стефано Санторо

VoltDB приветствует Groovy в своей экосистеме в качестве первого встроенного языка процедур. Код вашей логики процедуры прямо в DDL, обходя требования процедуры Java для отдельного редактирования / компиляции исходных файлов Java.

С помощью хранимых процедур VoltDB Groovy вы можете кодировать реализацию вашей процедуры как часть инструкций CREATE PROCEDURE в вашем файле DDL.

CREATE PROCEDURE groovy.procedures.Item AS ###
selectItem = new SQLStmt('SELECT ITEM_ID, DESCRIPTION FROM ITEMS WHERE ITEM_ID = ?')
transactOn = { id ->
    voltQueueSQL(selectItem, EXPECT_ZERO_OR_ONE_ROW, id)
    voltExecuteSQL(true)
}
### LANGUAGE GROOVY;
PARTITION PROCEDURE Item ON TABLE ITEMS COLUMN ITEM_ID;

Как написать заводную процедуру

Вы можете положиться на тот факт, что следующие процедуры импорта предопределены и доступны для процедур Groovy:

import static org.voltdb.VoltTypes.*
import static org.voltdb.VoltProcedure.*
import org.voltdb.*
import org.voltdb.groovy.TableBuilder
import org.voltdb.groovy.Tuplerator
import org.voltdb.VoltProcedure.VoltAbortException

Сначала вы определяете экземпляры SQLStmtэтого, как в Java, никогда не должны изменяться в коде процедуры. Это потому, что VoltDB размышляет, анализирует и создает план для этих заявлений.

Вам необходимо определить transactOnзакрытие. VoltDB вызывает это закрытие, когда фактически выполняет процедуру. Замыкание может принимать любые аргументы, которые его эквивалентный метод запуска Java может. Он также должен возвращать a VoltTable, или массив VoltTables, или длинное значение.

Если вам нужно прервать транзакцию во время выполнения закрытия транзакции, вам нужно выбросить VoltAbortException.

Также имейте в виду следующие рекомендации.

  • определить ваш SQLStmt вне transactOnзамыкания
  • использовать таблицы для сохранения любого состояния, которое должно существовать между вызовами процедур

Groovy Таблица читателей и строителей

Следующая процедура демонстрирует два дополнения, которые делают чтение и создание таблицы немного проще

CREATE PROCEDURE voter.procedures.ContestantWinningStates AS ###
        resultStmt = new SQLStmt('''
            SELECT contestant_number, state, SUM(num_votes) AS num_votes
            FROM v_votes_by_contestant_number_state
            GROUP BY contestant_number, state
            ORDER BY 2 ASC, 3 DESC, 1 ASC;
        ''')

        transactOn = { int contestantNumber, int max ->
            voltQueueSQL(resultStmt)

            results = []
            state = ""

            tuplerator(voltExecuteSQL()[0]).eachRow {
                isWinning = state != it[1]
                state = it[1]

                if (isWinning && it[0] == contestantNumber) {
                    results << [state: state, votes: it[2]]
                }         
            }         
            if (max > results.size) max = results.size
            buildTable(state:STRING, num_votes:BIGINT) {
                results.sort { a,b -> b.votes - a.votes }[0..<max].each {
                    row it.state, it.votes
                }
            }
        }
    ### LANGUAGE GROOVY;

tuplerator(VoltTable table)возвращает Groovy-оболочку вокруг VoltTable. Метод eachRow принимает замыкание, которое вызывается для каждой строки в таблице. Доступ к значениям столбца можно легко получить по индексу столбца или имени столбца. В приведенном выше примере «это» является неявным параметром, передаваемым закрытию eachRow, и «он» обращается к первому значению столбца для строки с помощью метода it[0]доступа. То же самое можно получить с помощью it['contestantNumber']или более просто с it.contestantNumber. Если вам необходимо получить доступ непосредственно к базовой таблице, вы можете сделать это, ссылаясь на его «планшетном» аксессоре: it.table.get(1,STRING). Доступны и другие методы rowAt(int rowNum), которые устанавливают курсор таблицы на указанную строку и reset()сбрасывают курсор таблицы. Например

tuplerator(voltExecuteSQL()[0]).atRow(0)['state']

получает значение столбца состояния для первой строки.

buildTable (имя столбца: карта типов) позволяет легко создавать таблицы. Параметры метода:

  • карта, где ключи — это имена столбцов, а значения — их соответствующие типы столбцов.
  • замыкание, при котором вызовы строк добавляют строки в базовую таблицу

Приведенный выше код показывает таблицу с двумя столбцами (state и num_votes), в которую передаются значения из коллекции результатов.

Оба могут быть объединены следующим образом, где результаты одной таблицы обрабатываются и передаются во вновь созданную таблицу:

CREATE PROCEDURE voter.procedures.GetStateHeatmap AS ###
    resultStmt = new SQLStmt('''
        SELECT contestant_number, state, SUM(num_votes) AS num_votes
        FROM v_votes_by_contestant_number_state
        GROUP BY contestant_number, state
        ORDER BY 2 ASC, 3 DESC, 1 ASC;
    ''')

    transactOn = {
        voltQueueSQL(resultStmt)

        state = ""

        buildTable(
            state:STRING, contestant_number:INTEGER, 
            num_votes:BIGINT, is_winning:TINYINT
        ) {
            tuplerator(voltExecuteSQL()[0]).eachRow {
                byte isWinning = state != it.state ? (byte)1 : (byte)0
                state = it.state

                row state, it.contestantNumber, it.numVotes, isWinning
            }
        }
    }
### LANGUAGE GROOVY;

Вышеприведенные выдержки из кода можно просмотреть, посетив пример DDL в нашем репозитории Github.

Процедуры Java все еще быстрее, чем процедуры Groovy

The syntactic conciseness that leverages the dynamic code interpretation capabilities of Groovy comes at a performance cost. Java should be your procedure implementation language choice if you really need to eek out as much performance as you can. But if you want something to get you started faster with VoltDB, and explore its features, and power, then Groovy procedures are a very good option.