[Перевод] database/sql биндинги для YDB в Go

YQL — это SQL-диалект, специфичный для базы данных YDBYQL требует заранее объявлять имена и типы параметров запроса. Это обеспечивает высокую производительность и корректное поведение. В синтаксисе YQL параметры необходимо перечислять явно с помощью инструкции DECLARE. И этот нюанс YDB может быть неожиданным для пользователей традиционных баз данных.

e4ddb4b5890a52e7588f76885799826e.png

Кроме того, поскольку таблицы YDB находятся в структуре, подобной виртуальной файловой системе, их имена могут быть довольно длинными. Существует PRAGMA TablePathPrefix, которая может охватить остальную часть запроса внутри заданного префикса, упрощая имена таблиц. Например, запрос к таблице »/local/path/to/tables/seasons» может выглядеть следующим образом:

DECLARE $title AS Text;
DECLARE $views AS Uint64;
SELECT season_id
FROM `/local/path/to/tables/seasons`
WHERE title LIKE $title AND views > $views;

Используя инструкцию PRAGMA, вы можете упростить префиксную часть в названии всех таблиц, участвующих в YQL-запросе:

PRAGMA TablePathPrefix("/local/path/to/tables/”);
DECLARE $title AS Text;
DECLARE $views AS Uint64;
SELECT season_id 
FROM seasons 
WHERE title LIKE $title AND views > $views;

Также YQLподдерживает только именованные параметры запроса. Это означает, что вы не можете использовать привычные нумерованные (с использованием плейсхолдеров типа »$1»,  »$2»,  »$3» и т.п.) или позиционные (с использованием плейсхолдеров »?») параметры запроса. Это может стать неожиданностью для пользователей, привыкших к синтаксису PostgreSQL или MySQL (или другой СУБД).

Как YDB Go SDK может помочь вам упростить такие запросы?

database/sql драйвер для YDB (часть YDB Go SDK) поддерживает биндинги (обогащение) запросов для:

  • указания PRAGMA TablePathPrefix

  • авто-выведения DECLARE для типов параметров

  • нумерованных или позиционных параметров запросов

Биндинги запросов могут быть явно включены на этапе инициализации драйвера с помощью опций коннектора или параметра строки подключения. По умолчанию database/sql драйвер для YDB не изменяет запросы.

Следующий пример (без включения биндингов запросов) демонстрирует работу с типами YDB явным образом:

package main 

import (
  "context"
  "database/sql"

  "github.com/ydb-platform/ydb-go-sdk/v3"
  "github.com/ydb-platform/ydb-go-sdk/v3/table"
  "github.com/ydb-platform/ydb-go-sdk/v3/table/types"
)

func main() {
  db := sql.Open("ydb", "grpc://localhost:2136/local")
  defer db.Close()
  row := db.QueryRowContext(context.TODO(), `
    PRAGMA TablePathPrefix("/local/path/to/my/folder");
    DECLARE $p0 AS Int32;
    DECLARE $p1 AS Utf8;
    SELECT $p0, $p1`, 
    sql.Named("$p0", 42), 
    table.ValueParam("$p1", types.TextValue("my string")),
  )
  ...
}

Как вы можете видеть, в этом примере также требовался импорт пакетов ydb-go-sdk и непосредственная работа с ними.

С включенными биндингами тот же результат может быть достигнут намного проще:

package main 

import (
  "context"
  "database/sql"

  // anonymous import for registering driver
  _ "github.com/ydb-platform/ydb-go-sdk/v3" 
)

func main() {
  var (
    ctx = context.TODO()
    db = sql.Open("ydb", "grpc://localhost:2136/local?"+
           "go_auto_bind="+
              "table_path_prefix(/local/path/to/my/folder),"+
              "declare,"+
              "positional"
    )
  )
  // cleanup resources on exit from func
  defer db.Close()
  // query with positional args
  row := db.QueryRowContext(ctx, `SELECT ?, ?`, 42, "my string")
  ...
}
package main 

import (
  "context"
  "database/sql"

  "github.com/ydb-platform/ydb-go-sdk/v3" 
)

func main() {
  var (
    ctx = context.TODO()
    nativeDriver = ydb.MustOpen(ctx, "grpc://localhost:2136/local")
    db = sql.OpenDB(ydb.MustConnector(nativeDriver,
      // bind pragma TablePathPrefix
      ydb.WithTablePathPrefix("/local/path/to/my/folder"),
      // bind parameters declare
      ydb.WithAutoDeclare(),
      // bind positional args
      ydb.WithPositionalArgs(),
    ))
  )
  // cleanup resources on exit from func
  defer nativeDriver.Close(ctx) 
  defer db.Close()
  // query with positional args
  row := db.QueryRowContext(ctx, `SELECT ?, ?`, 42, "my string")
  ...
}

В обоих случаях исходный простой запрос

SELECT ?, ?

на стороне драйвера будет обогащен до следующего:

-- bind TablePathPrefix 
PRAGMA TablePathPrefix("/local/path/to/my/folder");

-- bind declares
DECLARE $p0 AS Int32;
DECLARE $p1 AS Utf8;

-- origin query with positional args replacement
SELECT $p0, $p1

Этот обогащенный запрос будет отправлен в YDB вместо исходного.

Порядок объявления привязок (через параметры строки подключения или через опции коннектора) определяет порядок, в котором биндинги отрабатывают на стороне драйвера.

Дополнительные примеры включения биндингов запросов смотрите в документации ydb-go-sdk:

1) обогащение запроса прагмой TablePathPrefix:

2) авто-выведение типов параметров (DECLARE):

3) биндинг позиционных параметров:

4) биндинг нумерованных параметров:

Для глубокого понимания биндингов запросов смотрите также unit-тесты в ydb-go-sdk

Вы можете написать свои собственные unit-тесты для проверки корректности работы биндингов над вашими запросами следующим образом:

import (
  "testing"

  "github.com/stretchr/testify/require"

  "github.com/ydb-platform/ydb-go-sdk/v3"
  "github.com/ydb-platform/ydb-go-sdk/v3/table"
  "github.com/ydb-platform/ydb-go-sdk/v3/testutil"
)

func TestBinding(t *testing.T) { 
  binder := testutil.QueryBind(
    // bind pragma TablePathPrefix
    ydb.WithTablePathPrefix("/local/path/to/my/folder"),
    // bind parameters declare
    ydb.WithAutoDeclare(),
    // auto-replace positional args
    ydb.WithPositionalArgs(),
  )
  query, params, err := binder.RewriteQuery(
    "SELECT ?, ?, ?", 
    1, 
    uint64(2), 
    "3",
  )
  require.NoError(t, err)
  require.Equal(t, `-- bind TablePathPrefix
PRAGMA TablePathPrefix("/local/path/to/my/folder”);

-- bind declares
DECLARE $p0 AS Int32;
DECLARE $p1 AS Uint64;
DECLARE $p2 AS Utf8;

-- origin query with positional args replacement
SELECT $p0, $p1, $p2`, 
    query,
  )
  require.Equal(t, 
    table.NewQueryParameters(
      table.ValueParam("$p0", types.Int32Value(1)),
      table.ValueParam("$p1", types.Uint64Value(2)),
      table.ValueParam("$p2", types.TextValue("3")),
    ), 
    params,
  )
}

Биндинги запросов в database/sql драйвере для YDB доступны начиная с версии v3.44.0 ydb-go-sdk. Пробуйте и возвращайтесь с обратной связью!

Если у вас возникнут какие-либо трудности или вопросы, пожалуйста, не стесняйтесь обращаться к нам через:

© Habrahabr.ru