github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/SQL.md (about)

     1  # `database/sql` driver for `YDB`
     2  
     3  In addition to native YDB Go driver APIs, package `ydb-go-sdk` provides standard APIs for `database/sql`.
     4  It allows to use "regular" Go facilities to access YDB databases.
     5  Behind the scene, `database/sql` APIs are implemented using the native interfaces.
     6  
     7  
     8  ## Table of contents
     9  1. [Initialization of `database/sql` driver](#init)
    10     * [Configure driver with `ydb.Connector` (recommended way)](#init-connector)
    11     * [Configure driver with data source name or connection string](#init-dsn)
    12  2. [Client balancing](#balancing)
    13  3. [Session pooling](#session-pool)
    14  4. [Query execution](#queries)
    15     * [Queries on database object](#queries-db)
    16     * [Queries on transaction object](#queries-tx)
    17  5. [Query modes (DDL, DML, DQL, etc.)](#query-modes)
    18  6. [Retry helpers for `YDB` `database/sql` driver](#retry)
    19     * [Over `sql.Conn` object](#retry-conn)
    20     * [Over `sql.Tx`](#retry-tx)
    21  7. [Query args types](#arg-types)
    22  8. [Query bindings](#bindings)
    23  9. [Accessing the native driver from `*sql.DB`](#unwrap)
    24     * [Driver with go's 1.18 supports also `*sql.Conn` for unwrapping](#unwrap-cc)
    25  10. [Troubleshooting](#troubleshooting)
    26     * [Logging driver events](#logging)
    27     * [Add metrics about SDK's events](#metrics)
    28     * [Add `Jaeger` traces about driver events](#jaeger)
    29  11. [Example of usage](#example)
    30  
    31  ## Initialization of `database/sql` driver <a name="init"></a>
    32  
    33  ### Configure driver with `ydb.Connector` (recommended way) <a name="init-connector"></a>
    34  ```go
    35  import (
    36      "database/sql"
    37    
    38      "github.com/ydb-platform/ydb-go-sdk/v3"
    39  )
    40  
    41  func main() {
    42      // init native ydb-go-sdk driver
    43      nativeDriver, err := ydb.Open(context.TODO(), "grpc://localhost:2136/local",
    44          // See many ydb.Option's for configure driver https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#Option
    45      )
    46      if err != nil {
    47          // fallback on error
    48      }
    49      defer nativeDriver.Close(context.TODO())
    50      connector, err := ydb.Connector(nativeDriver,
    51          // See ydb.ConnectorOption's for configure connector https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#ConnectorOption
    52      )
    53      if err != nil {
    54          // fallback on error
    55      }
    56      db := sql.OpenDB(connector)
    57      defer db.Close()
    58      db.SetMaxOpenConns(100)
    59      db.SetMaxIdleConns(100)
    60      db.SetConnMaxIdleTime(time.Second) // workaround for background keep-aliving of YDB sessions
    61  }
    62  ```
    63  
    64  ### Configure driver with data source name or connection string <a name="init-dsn"></a>
    65  ```go
    66  import (
    67      "database/sql"
    68    
    69      _ "github.com/ydb-platform/ydb-go-sdk/v3"
    70  )
    71  
    72  func main() {
    73      db, err := sql.Open("ydb", "grpc://localhost:2136/local")
    74      defer db.Close()
    75  }
    76  ```
    77  Data source name parameters:
    78  * `token` – access token to be used during requests (required)
    79  * static credentials with authority part of URI, like `grpcs://root:password@localhost:2135/local`
    80  * `query_mode=scripting` - you can redefine default [DML](https://en.wikipedia.org/wiki/Data_manipulation_language) query mode
    81  
    82  ## Client balancing <a name="balancing"></a>
    83  
    84  `database/sql` driver for `YDB` like as native driver for `YDB` use client balancing, which happens on `CreateSession` request.
    85  At this time, native driver choose known node for execute request by according balancer algorithm.
    86  Default balancer algorithm is a `random choice`.
    87  Client balancer may be re-configured with option `ydb.WithBalancer`:
    88  ```go
    89  import (
    90      "github.com/ydb-platform/ydb-go-sdk/v3/balancers"
    91  )
    92  func main() {
    93      nativeDriver, err := ydb.Open(context.TODO(), "grpc://localhost:2136/local",
    94          ydb.WithBalancer(
    95              balancers.PreferLocationsWithFallback(
    96                  balancers.RandomChoice(), "a", "b",
    97              ),
    98          ),
    99      )
   100      if err != nil {
   101          // fallback on error
   102      }
   103      connector, err := ydb.Connector(nativeDriver)
   104      if err != nil {
   105          // fallback on error
   106      }
   107      db := sql.OpenDB(connector)
   108      db.SetMaxOpenConns(100)
   109      db.SetMaxIdleConns(100)
   110      db.SetConnMaxIdleTime(time.Second) // workaround for background keep-aliving of YDB sessions
   111  }
   112  ```
   113  
   114  ## Session pooling <a name="session-pool"></a>
   115  
   116  Native driver `ydb-go-sdk/v3` implements the internal session pool, which uses with `db.Table().Do()` or `db.Table().DoTx()` methods.
   117  Internal session pool are configured with options like `ydb.WithSessionPoolSizeLimit()` and other.
   118  Unlike the session pool in the native driver, `database/sql` contains a different implementation of the session pool, which is configured with `*sql.DB.SetMaxOpenConns` and `*sql.DB.SetMaxIdleConns`.
   119  Lifetime of a `YDB` session depends on driver configuration and error occurance, such as `sql.driver.ErrBadConn`.
   120  `YDB` driver for `database/sql` includes the logic to transform the internal `YDB` error codes into `sql.driver.ErrBadConn` and other retryable and non-retryable error types.
   121  
   122  In most cases the implementation of `database/sql` driver for YDB allows to complete queries without user-visible errors.
   123  But some errors need to be handled on the client side, by re-running not a single operation, but a complete transaction.
   124  Therefore the transaction logic needs to be wrapped with retry helpers, such as `retry.Do` or `retry.DoTx` (see more about retry helpers in the [retry section](#retry)).
   125  
   126  ## Query execution <a name="queries"></a>
   127  
   128  ### Queries on database object <a name="queries-db"></a>
   129  ```go
   130  rows, err := db.QueryContext(ctx,
   131      "SELECT series_id, title, release_date FROM `/full/path/of/table/series`;"
   132  )
   133  if err != nil {
   134      log.Fatal(err)
   135  }
   136  defer rows.Close() // always close rows 
   137  var (
   138      id          *string
   139      title       *string
   140      releaseDate *time.Time
   141  )
   142  for rows.Next() { // iterate over rows
   143      // apply database values to go's type destinations
   144      if err = rows.Scan(&id, &title, &releaseDate); err != nil {
   145          log.Fatal(err)
   146      }
   147      log.Printf("> [%s] %s (%s)", *id, *title, releaseDate.Format("2006-01-02"))
   148  }
   149  if err = rows.Err(); err != nil { // always check final rows err
   150      log.Fatal(err)
   151  }
   152  ```
   153  
   154  ### Queries on transaction object <a name="queries-tx"></a>
   155  
   156  `database/sql` driver over `ydb-go-sdk/v3` supports next isolation leveles:
   157  - read-write (mapped to `SerializableReadWrite` transaction control)
   158    ```go
   159    rw := sql.TxOption{
   160      ReadOnly: false,
   161      Isolation: sql.LevelDefault,
   162    }
   163    ```
   164  - read-only (mapped to `OnlineReadOnly` transaction settings on each request, will be mapped to true `SnapshotReadOnly` soon)
   165    ```go
   166    ro := sql.TxOption{
   167      ReadOnly: true,
   168      Isolation: sql.LevelSnapshot,
   169    }
   170    ```
   171  
   172  Example of works with transactions:
   173  ```go
   174  tx, err := db.BeginTx(ctx, sql.TxOption{
   175    ReadOnly: true,
   176    Isolation: sql.LevelSnapshot,
   177  })
   178  if err != nil {
   179      log.Fatal(err)
   180  }
   181  defer tx.Rollback()
   182  rows, err := tx.QueryContext(ctx,
   183      "SELECT series_id, title, release_date FROM `/full/path/of/table/series`;"
   184  )
   185  if err != nil {
   186      log.Fatal(err)
   187  }
   188  defer rows.Close() // always close rows 
   189  var (
   190      id          *string
   191      title       *string
   192      releaseDate *time.Time
   193  )
   194  for rows.Next() { // iterate over rows
   195      // apply database values to go's type destinations
   196      if err = rows.Scan(&id, &title, &releaseDate); err != nil {
   197          log.Fatal(err)
   198      }
   199      log.Printf("> [%s] %s (%s)", *id, *title, releaseDate.Format("2006-01-02"))
   200  }
   201  if err = rows.Err(); err != nil { // always check final rows err
   202      log.Fatal(err)
   203  }
   204  if err = tx.Commit(); err != nil {
   205      log.Fatal(err)
   206  }
   207  ```
   208  
   209  ## Query modes (DDL, DML, DQL, etc.) <a name="query-modes"></a>
   210  Currently the `YDB` server APIs require the use of a proper GRPC service method depending on the specific request type.
   211  In particular, [DDL](https://en.wikipedia.org/wiki/Data_definition_language) must be called through `table.session.ExecuteSchemeQuery`,
   212  [DML](https://en.wikipedia.org/wiki/Data_manipulation_language) needs `table.session.Execute`, and
   213  [DQL](https://en.wikipedia.org/wiki/Data_query_language) should be passed via `table.session.Execute` or `table.session.StreamExecuteScanQuery`.
   214  `YDB` also has a so-called "scripting" service, which supports different query types within a single method,
   215  but without support for transactions.
   216  
   217  Unfortunately, this leads to the need to choose the proper query mode on the application side.
   218  
   219  `YDB` team has a roadmap goal to implement a single universal service method for executing
   220  different query types and without the limitations of the "scripting" service method.
   221  
   222  `database/sql` driver implementation for `YDB` supports the following query modes:
   223  * `ydb.DataQueryMode` - default query mode, for lookup [DQL](https://en.wikipedia.org/wiki/Data_query_language) queries and [DML](https://en.wikipedia.org/wiki/Data_manipulation_language) queries.
   224  * `ydb.ExplainQueryMode` - for gettting plan and [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of the query
   225  * `ydb.ScanQueryMode` - for heavy [OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing) style scenarious, with [DQL-only](https://en.wikipedia.org/wiki/Data_query_language) queries. Read more about scan queries in [ydb.tech](https://ydb.tech/en/docs/concepts/scan_query)
   226  * `ydb.SchemeQueryMode` - for [DDL](https://en.wikipedia.org/wiki/Data_definition_language) queries
   227  * `ydb.ScriptingQueryMode` - for [DDL](https://en.wikipedia.org/wiki/Data_definition_language), [DML](https://en.wikipedia.org/wiki/Data_manipulation_language), [DQL](https://en.wikipedia.org/wiki/Data_query_language) queries (not a [TCL](https://en.wikipedia.org/wiki/SQL#Transaction_controls)). Be careful: queries execute longer than with other query modes, and consume more server-side resources
   228  
   229  Example for changing the default query mode:
   230  ```go
   231  res, err = db.ExecContext(ydb.WithQueryMode(ctx, ydb.SchemeQueryMode),
   232     "DROP TABLE `/full/path/to/table/series`",
   233  )
   234  ```
   235  
   236  ## Changing the transaction control mode <a name="tx-control"></a>
   237  
   238  Default `YDB`'s transaction control mode is a `SerializableReadWrite`. 
   239  Default transaction control mode can be changed outside of interactive transactions by updating the context object:
   240  ```go
   241  rows, err := db.QueryContext(ydb.WithTxControl(ctx, table.OnlineReadOnlyTxControl()),
   242      "SELECT series_id, title, release_date FROM `/full/path/of/table/series`;"
   243  )
   244  ```
   245  
   246  ## Retry helpers for `YDB` `database/sql` driver <a name="retry"></a>
   247  
   248  `YDB` is a distributed `RDBMS` with non-stop 24/7 releases flow.
   249  It means some nodes may be unavailable for queries at some point in time.
   250  Network errors may also occur.
   251  That's why some queries may complete with errors.
   252  Most of those errors are transient.
   253  `ydb-go-sdk`'s "knows" what to do on specific error: retry or not, with or without backoff, with or without the need to re-establish the session, etc.
   254  `ydb-go-sdk` provides retry helpers which can work either with the database connection object, or with the transaction object.
   255  
   256  ### Retries over `sql.Conn` object <a name="retry-conn"></a>
   257  
   258  `retry.Do` helper accepts custom lambda, which must return error if it happens during the processing,
   259  or nil if the operation succeeds.
   260  ```go
   261  import (
   262     "github.com/ydb-platform/ydb-go-sdk/v3/retry"
   263  )
   264  ...
   265  err := retry.Do(context.TODO(), db, func(ctx context.Context, cc *sql.Conn) error {
   266     // work with cc
   267     rows, err := cc.QueryContext(ctx, "SELECT 1;")
   268     if err != nil {
   269         return err // return err to retryer
   270     }
   271     ...
   272     return nil // good final of retry operation
   273  }, retry.WithIdempotent(true))
   274  
   275  ```
   276  
   277  ### Retries over `sql.Tx` <a name="retry-tx"></a>
   278  
   279  `retry.DoTx` helper accepts custom lambda, which must return error if it happens during processing,
   280  or nil if the operation succeeds.
   281  
   282  `tx` object is a prepared transaction object.
   283  
   284  The logic within the custom lambda does not need the explicit commit or rollback at the end - `retry.DoTx` does it automatically.
   285  
   286  ```go
   287  import (
   288      "github.com/ydb-platform/ydb-go-sdk/v3/retry"
   289  )
   290  ...
   291  err := retry.DoTx(context.TODO(), db, func(ctx context.Context, tx *sql.Tx) error {
   292      // work with tx
   293      rows, err := tx.QueryContext(ctx, "SELECT 1;")
   294      if err != nil {
   295          return err // return err to retryer
   296      }
   297      ...
   298      return nil // good final of retry tx operation
   299  }, retry.WithIdempotent(true), retry.WithTxOptions(&sql.TxOptions{
   300      Isolation: sql.LevelSnapshot,
   301      ReadOnly:  true,
   302  }))
   303  ```
   304  
   305  ## Specifying query parameters <a name="arg-types"></a>
   306  
   307  `database/sql` driver for `YDB` supports the following types of query parameters:
   308  * `sql.NamedArg` types with native Go's types and ydb types:
   309     ```go
   310     rows, err := db.QueryContext(ctx, 
   311        `
   312          DECLARE $title AS Text;
   313          DECLARE $views AS Uint64;
   314          DECLARE $ts AS Datetime;
   315          SELECT season_id FROM seasons WHERE title LIKE $title AND views > $views AND first_aired > $ts;
   316        `,
   317        sql.Named("$title", "%Season 1%"), // argument name with prefix `$` 
   318        sql.Named("views", uint64(1000)),  // argument name without prefix `$` (driver will prepend `$` if necessary)
   319        sql.Named("$ts", types.DatetimeValueFromTime( // native ydb type
   320          time.Now().Add(-time.Hour*24*365), 
   321        )),
   322     )
   323     ```
   324  * `table.ParameterOption` arguments:
   325     ```go
   326     rows, err := db.QueryContext(ctx, 
   327        `
   328          DECLARE $title AS Text;
   329          DECLARE $views AS Uint64;
   330          SELECT season_id FROM seasons WHERE title LIKE $title AND views > $views;
   331        `,
   332        table.ValueParam("$seasonTitle", types.TextValue("%Season 1%")),
   333        table.ValueParam("$views", types.Uint64Value((1000)),
   334     )
   335     ```
   336  * single `*table.QueryParameters` argument:
   337     ```go
   338     rows, err := db.QueryContext(ctx, 
   339        `
   340          DECLARE $title AS Text;
   341          DECLARE $views AS Uint64;
   342          SELECT season_id FROM seasons WHERE title LIKE $title AND views > $views;
   343        `,
   344        table.NewQueryParameters(
   345            table.ValueParam("$seasonTitle", types.TextValue("%Season 1%")),
   346            table.ValueParam("$views", types.Uint64Value((1000)),
   347        ),
   348     )
   349     ```
   350  
   351  ## Query bindings <a name="bindings"></a>
   352  
   353  YQL is a SQL dialect with YDB specific strict types. This is great for performance and correctness, but sometimes can be a bit dauting to express in a query, especially when then need to be parametrized externally from the application side. For instance, when a YDB query needs to be parametrized, each parameter has name and type provided via `DECLARE` statement.
   354  
   355  Also, because YDB tables reside in virtual filesystem-like structure their names can be quite lengthy. There's a `PRAGMA TablePathPrefix` that can scope the rest of the query inside a given prefix, simplifying table names. For example, a query to the table `/local/path/to/tables/seasons` might look like this:
   356  
   357  ```
   358  DECLARE $title AS Text;
   359  DECLARE $views AS Uint64;
   360  SELECT season_id
   361  FROM `/local/path/to/tables/seasons`
   362  WHERE title LIKE $title AND views > $views;
   363  ```
   364  
   365  Using the `PRAGMA` statement, you can simplify the prefix part in the name of all tables involved in the `YQL`-query:
   366  
   367  ```
   368  PRAGMA TablePathPrefix("/local/path/to/tables/");
   369  DECLARE $title AS Text;
   370  DECLARE $views AS Uint64;
   371  SELECT season_id FROM seasons WHERE title LIKE $title AND views > $views;
   372  ```
   373  
   374  `database/sql` driver for `YDB` (part of [YDB Go SDK](https://github.com/ydb-platform/ydb-go-sdk)) supports query enrichment for:
   375  
   376  * specifying **_TablePathPrefix_**
   377  * **_declaring_** types of parameters
   378  * **_numeric_** or **_positional_** parameters
   379  
   380  These query enrichments can be enabled explicitly on the initializing step using connector options or connection string parameter `go_auto_bind`. By default `database/sql` driver for `YDB` doesn’t modify queries.
   381  
   382  The following example without bindings demonstrates explicit working with `YDB` types:
   383  
   384  ```go
   385  import (
   386    "context"
   387    "database/sql"
   388  
   389    "github.com/ydb-platform/ydb-go-sdk/v3"
   390    "github.com/ydb-platform/ydb-go-sdk/v3/table"
   391    "github.com/ydb-platform/ydb-go-sdk/v3/table/types"
   392  )
   393  
   394  func main() {
   395    db := sql.Open("ydb", "grpc://localhost:2136/local")
   396    defer db.Close()
   397  
   398    // positional args
   399    row := db.QueryRowContext(context.TODO(), `
   400      PRAGMA TablePathPrefix("/local/path/to/my/folder");
   401      DECLARE $p0 AS Int32;
   402      DECLARE $p1 AS Utf8;
   403      SELECT $p0, $p1`, 
   404      sql.Named("$p0", 42), 
   405      table.ValueParam("$p1", types.TextValue("my string")),
   406    )
   407    // process row ...
   408  }
   409  ```
   410  
   411  As you can see, this example also required importing of `ydb-go-sdk` packages and working with them directly.
   412  
   413  With enabled bindings enrichment, the same result can be achieved much easier:
   414  
   415  - with _**connection string_** params:
   416  ```go
   417  import (
   418    "context"
   419    "database/sql"
   420  
   421    _ "github.com/ydb-platform/ydb-go-sdk/v3" // anonymous import for registering driver
   422  )
   423  
   424  func main() {
   425    var (
   426      ctx = context.TODO()
   427      db = sql.Open("ydb", 
   428        "grpc://localhost:2136/local?"+
   429          "go_auto_bind="+
   430            "table_path_prefix(/local/path/to/my/folder),"+
   431            "declare,"+
   432            "positional", 
   433      )
   434    )
   435    defer db.Close() // cleanup resources
   436  
   437    // positional args
   438    row := db.QueryRowContext(ctx, `SELECT ?, ?`, 42, "my string")
   439  ```
   440  
   441  - with _**connector options**_:
   442  ```go
   443  import (
   444    "context"
   445    "database/sql"
   446  
   447    "github.com/ydb-platform/ydb-go-sdk/v3" 
   448  )
   449  
   450  func main() {
   451    var (
   452      ctx = context.TODO()
   453      nativeDriver = ydb.MustOpen(ctx, "grpc://localhost:2136/local")
   454      db = sql.OpenDB(ydb.MustConnector(nativeDriver,
   455        query.TablePathPrefix("/local/path/to/my/folder"), // bind pragma TablePathPrefix
   456        query.Declare(),                                   // bind parameters declare
   457        query.Positional(),                                // bind positional args
   458      ))
   459    )
   460    defer nativeDriver.Close(ctx) // cleanup resources
   461    defer db.Close()
   462  
   463    // positional args
   464    row := db.QueryRowContext(ctx, `SELECT ?, ?`, 42, "my string")
   465  ```
   466  
   467  The original simple query `SELECT ?, ?` will be expanded on the driver side to the following:
   468  
   469  ```sql
   470  -- bind TablePathPrefix 
   471  PRAGMA TablePathPrefix("/local/path/to/my/folder");
   472  
   473  -- bind declares
   474  DECLARE $p0 AS Int32;
   475  DECLARE $p1 AS Utf8;
   476  
   477  -- origin query with positional args replacement
   478  SELECT $p0, $p1
   479  ```
   480  
   481  This expanded query will be sent to `ydbd` server instead of the original one.
   482  
   483  For additional examples of query enrichment, see `ydb-go-sdk` documentation:
   484  
   485  * specifying `TablePathPrefix`:
   486      * using [connection string parameter](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindTablePathPrefix)
   487      * using [connector option](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindTablePathPrefixOverConnector)
   488  * declaring bindings:
   489      * using [connection string parameter](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindDeclare)
   490      * using [connector option](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindDeclareOverConnector)
   491  * positional arguments binding:
   492      * using [connection string parameter](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindPositionalArgs)
   493      * using [connector option](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindPositionalArgsOverConnector)
   494  * numeric arguments binding:
   495      * using [connection string parameter](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindNumericlArgs)
   496      * using [connector option](https://pkg.go.dev/github.com/ydb-platform/ydb-go-sdk/v3#example-package-DatabaseSQLBindNumericArgsOverConnector)
   497  
   498  For a deep understanding of query enrichment see also [unit-tests](https://github.com/ydb-platform/ydb-go-sdk/blob/master/query/bind_test.go).
   499  
   500  You can write your own unit tests to check correct binding of your queries like this:
   501  
   502  ```go
   503  func TestBinding(t *testing.T) {
   504    bindings := query.NewBind(
   505      query.TablePathPrefix("/local/path/to/my/folder"), // bind pragma TablePathPrefix
   506      query.Declare(),                                   // bind parameters declare
   507      query.Positional(),                                // auto-replace positional args
   508    )
   509    query, params, err := bindings.ToYQL("SELECT ?, ?, ?", 1, uint64(2), "3")
   510    require.NoError(t, err)
   511    require.Equal(t, `-- bind TablePathPrefix
   512  PRAGMA TablePathPrefix("/local/path/to/my/folder");
   513  
   514  -- bind declares
   515  DECLARE $p0 AS Int32;
   516  DECLARE $p1 AS Uint64;
   517  DECLARE $p2 AS Utf8;
   518  
   519  -- origin query with positional args replacement
   520  SELECT $p0, $p1, $p2`, query)
   521    require.Equal(t, table.NewQueryParameters(
   522      table.ValueParam("$p0", types.Int32Value(1)),
   523      table.ValueParam("$p1", types.Uint64Value(2)),
   524      table.ValueParam("$p2", types.TextValue("3")),
   525    ), params)
   526  }
   527  ```
   528  
   529  ## Accessing the native driver from `*sql.DB` <a name="unwrap"></a>
   530  
   531  ```go
   532  db, err := sql.Open("ydb", "grpc://localhost:2136/local")
   533  if err != nil {
   534    t.Fatal(err)
   535  }
   536  nativeDriver, err = ydb.Unwrap(db)
   537  if err != nil {
   538    t.Fatal(err)
   539  }
   540  nativeDriver.Table().Do(ctx, func(ctx context.Context, s table.Session) error {
   541    // doing with native YDB session
   542    return nil
   543  })
   544  ```
   545  
   546  ### Driver with go's 1.18 supports also `*sql.Conn` for unwrapping <a name="unwrap-cc"></a>
   547  
   548  For example, this feature may be helps with `retry.Do`:
   549  ```go
   550  err := retry.Do(context.TODO(), db, func(ctx context.Context, cc *sql.Conn) error {
   551      nativeDriver, err := ydb.Unwrap(cc)
   552      if err != nil {
   553          return err // return err to retryer
   554      }
   555      res, err := nativeDriver.Scripting().Execute(ctx,
   556          "SELECT 1+1",
   557          table.NewQueryParameters(),
   558      )
   559      if err != nil {
   560          return err // return err to retryer
   561      }
   562      // work with cc
   563      rows, err := cc.QueryContext(ctx, "SELECT 1;")
   564      if err != nil {
   565          return err // return err to retryer
   566      }
   567      ...
   568      return nil // good final of retry operation
   569  }, retry.WithIdempotent(true))
   570  ```
   571  
   572  ## Troubleshooting <a name="troubleshooting"></a>
   573  
   574  ### Logging driver events <a name="logging"></a>
   575  
   576  Adding a logging driver events allowed only if connection to `YDB` opens over [connector](##init-connector).
   577  Adding of logging provides with [debug adapters](README.md#debug) and wrotes in [migration notes](MIGRATION_v2_v3.md#logs).
   578  
   579  Example of adding `zap` logging:
   580  ```go
   581  import (
   582      "github.com/ydb-platform/ydb-go-sdk/v3/trace"
   583      ydbZap "github.com/ydb-platform/ydb-go-sdk-zap"
   584  )
   585  ...
   586  nativeDriver, err := ydb.Open(ctx, connectionString,
   587      ...
   588      ydbZap.WithTraces(log, trace.DetailsAll),
   589  )
   590  if err != nil {
   591      // fallback on error
   592  }
   593  connector, err := ydb.Connector(nativeDriver)
   594  if err != nil {
   595      // fallback on error
   596  }
   597  db := sql.OpenDB(connector)
   598  ```
   599  
   600  ### Add metrics about SDK's events <a name="metrics"></a>
   601  
   602  Adding of driver events monitoring allowed only if connection to `YDB` opens over [connector](##init-connector).
   603  Monitoring of driver events provides with [debug adapters](README.md#debug) and wrotes in [migration notes](MIGRATION_v2_v3.md#metrics).
   604  
   605  Example of adding `Prometheus` monitoring:
   606  ```go
   607  import (
   608      "github.com/ydb-platform/ydb-go-sdk/v3/trace"
   609      ydbMetrics "github.com/ydb-platform/ydb-go-sdk-prometheus"
   610  )
   611  ...
   612  nativeDriver, err := ydb.Open(ctx, connectionString,
   613      ...
   614      ydbMetrics.WithTraces(registry, ydbMetrics.WithDetails(trace.DetailsAll)),
   615  )
   616  if err != nil {
   617      // fallback on error
   618  }
   619  connector, err := ydb.Connector(nativeDriver)
   620  if err != nil {
   621      // fallback on error
   622  }
   623  db := sql.OpenDB(connector)
   624  ```
   625  
   626  ### Add `Jaeger` traces about driver events <a name="jaeger"></a>
   627  
   628  Adding of `Jaeger` traces about driver events allowed only if connection to `YDB` opens over [connector](##init-connector).
   629  `Jaeger` tracing provides with [debug adapters](README.md#debug) and wrotes in [migration notes](MIGRATION_v2_v3.md#jaeger).
   630  
   631  Example of adding `Jaeger` tracing:
   632  ```go
   633  import (
   634      "github.com/ydb-platform/ydb-go-sdk/v3/trace"
   635      ydbOpentracing "github.com/ydb-platform/ydb-go-sdk-opentracing"
   636  )
   637  ...
   638  nativeDriver, err := ydb.Open(ctx, connectionString,
   639      ...
   640      ydbOpentracing.WithTraces(trace.DriverConnEvents | trace.DriverClusterEvents | trace.DriverRepeaterEvents | trace.DiscoveryEvents),
   641  )
   642  if err != nil {
   643      // fallback on error
   644  }
   645  connector, err := ydb.Connector(nativeDriver)
   646  if err != nil {
   647      // fallback on error
   648  }
   649  db := sql.OpenDB(connector)
   650  ```
   651  
   652  ## Example of usage <a name="example"></a>
   653  
   654  [Basic example](https://github.com/ydb-platform/ydb-go-examples/tree/master/basic) about series written with `database/sql` driver for `YDB` placed in [examples repository](https://github.com/ydb-platform/ydb-go-examples/tree/master/database_sql)