安装 Knex.js

Knex 是一个可以运行于NodeJS环境以及浏览器环境下的 SQL 查询构建工具(但受 WebSQL 的 限制,如删除表、读取表结构等操作是无法捃的),但强烈建议不要随意在浏览器中执行SQL查询,因为这会导致严重的安全漏洞,还是建议您在服务器端执行,在浏览器端执行查询,主要还是用于学习。

Node.js

Node.js 是 Knex 的首先运行环境,你需要在你的 Node.js 项目中安装 knex 库,同时还需要安装适当的数据库操作库:

  • PostgreSQL,CockroachDB 以及 Amazon Redshift 数据库使用 pg
  • PostgreSQl 的原生 C++ 库 libpq 则使用 pg-native 库绑定
  • MySQL 以及 MariaDB 使用 mysql
  • SQLite3 使用 sqlite3 或者 better-sqlite3
  • MSSQL 使用 tedious
$ npm install knex --save

# Then add one of the following (adding a --save) flag:
$ npm install pg
$ npm install pg-native
$ npm install sqlite3
$ npm install better-sqlite3
$ npm install mysql
$ npm install mysql2
$ npm install oracledb
$ npm install tedious

浏览器

您可以使用 browserify 或者 webpack 这样的 JavaScript 构建工具,将 Knex 构建至您的浏览器项目中。

配置选项

knex 模块本身是一个用于构建 Knex 实例的函数,您需要将配置信息通过一个对象传递给该函数,配置信息是一个对象,该对象的 client 属性是必须的,它指明了新创建的 Knex 实例将使用哪一个数据库。

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host: '127.0.0.1',
    port: 3306,
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test',
  },
});

connection 选项将直接被传递给数据库驱动,它可以是一个对象、一个连接字符串或者一个返回一个对象的函数。

PostgreSQL

对于 PostgreSQL 数据库, Knex 允许你通过添加一个名为 searchPath 参数给每一个数据库连接。
const pg = require('knex')({
  client: 'pg',
  connection: process.env.PG_CONNECTION_STRING,
  searchPath: ['knex', 'public'],
});

当使用 PostgreSQL 驱动时,实例化 Knex 的配置对象 connection: {} 可以通过各种标签指定各种详细的参数,比如是否启用 SSL、一个连接字符串,如下例:

PostgreSQL

如果存在 connectionString 属性,则会优先使用该属性,否则,连接将使用多个独立的字段组合而成的配置(hostport 等),最后,还可以使用 ssl 属性来指定是否启用 SSL 连接。

const pg = require('knex')({
  client: 'pg',
  connection: {
    connectionString: config.DATABASE_URL,
    host: config['DB_HOST'],
    port: config['DB_PORT'],
    user: config['DB_USER'],
    database: config['DB_NAME'],
    password: config['DB_PASSWORD'],
    ssl: config['DB_SSL'] ? { rejectUnauthorized: false } : false,
  },
});

下面则演示了如何使用 SQLite3 数据库

SQLite3 或 Better-SQLite3

当你使用 SQLite3 或者 Better-SQLite3 适配器的时候,filename 属性是必须项:

const knex = require('knex')({
  client: 'sqlite3', // 或者 'better-sqlite3'
  connection: {
    filename: './mydb.sqlite',
  }
});

你也可以通过指定 filename:memory:,来启用一个连接一个临时保存于内存中的数据库连接实例:

const knex = require('knex')({
  client: 'sqlite3', // 或者 'better-sqlite3'
  connection: {
    filename: ':memory:',
  }
});

SQLite3

当你使用 SQLite3 适配器时,你可以通过添加 flags 属性设置连接的标签:

const knex = require('knex')({
  client: 'sqlite3',
  connection: {
    filename: 'file:memDb1?mode=memory&cache=shared',
    flags: ['OPEN_URI', 'OPEN_SHAREDCACHE']
  }
});

Better-SQLite3

当你使用 Better-SQLite3 适配器时,可以通过 options.nativeBindding 属性来绑定原生 C++ 适配器的地址:

const knex = require('knex')({
  client: 'better-sqlite3',
  connection: {
    filename: ':memory:',
    options: {
      nativeBinding: '/path/to/better_sqlite3.node',
    },
  },
});

通过将 options.readonly 设置为 true 来开启一个只读连接:

const knex = require('knex')({
  client: 'better-sqlite3',
  connection: {
    filename: '/path/to/db.sqlite3',
    options: {
      readonly: true,
    },
  },
});

MSSQL

使用 MSSQL 数据库时,您可以通过指定 mapBindding 函数来定义自己的逻辑来定义 knex 查询参数到 tedious 类型的绑定:

import { TYPES } from 'tedious';

const knex = require('knex')({
  client: 'mssql',
  connection: {
    options: {
      mapBinding: (value) => {
        // bind all strings to varchar instead of nvarchar
        if (typeof value === 'string') {
          return {
            type: TYPES.VarChar,
            value,
          };
        }

        // allow devs to pass tedious type at query time
        if (value != null && value.type) {
          return {
            type: value.type,
            value: value.value,
          };
        }

        // undefined is returned; falling back to default mapping function
      },
    },
  },
});

有用的信息

当您使用 PostgreSQL 适配器连接非标准数据库时,可以在 knex 配置中添加数据库版本。

const knex = require('knex')({
  client: 'pg',
  version: '7.2',
  connection: {
    host: '127.0.0.1',
    port: 5432,
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test',
  },
});
const knex = require('knex')({
  client: 'mysql',
  version: '5.7',
  connection: {
    host: '127.0.0.1',
    port: 3306,
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test',
  },
});

我们也可以使用一个函数动态的给 connection 属性赋值,该函数只需要返回一个配置对象,或者一个返回配置对象的 Promise 即可:

const knex = require('knex')({
  client: 'sqlite3',
  connection: () => ({
    filename: process.env.SQLITE_FILENAME,
  }),
});

Knex 在默认情况下,会将函数返回的结果缓存起来,后面的所有连接都将使用该缓存,您可以通过指定一个 expirationChecker 函数用于判断缓存是否已过期,Knex 会在每一次创建新连接之前,首先调用 expirationChecker 函数,若其返回 true,则会清除缓存,并重新获取一次新的数据。

const knex = require('knex')({
  client: 'postgres',
  connection: async () => {
    const { token, tokenExpiration } = await someCallToGetTheToken();

    return {
      host: 'your_host',
      port: 5432,
      user: 'your_database_user',
      password: token,
      database: 'myapp_test',
      expirationChecker: () => {
        return tokenExpiration <= Date.now();
      },
    };
  },
});

你也可以指定一个 Unix Socket 地址,此时 porthost 会被直接忽略。

const knex = require('knex')({
  client: 'mysql',
  connection: {
    socketPath: '/path/to/socket.sock',
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test',
  },
});

通过在配置信息中指定 userParams 参数,可以向 Knex 新创建的实例中添加随意的参数,这些参数可以通过实例对象 knex.userParams 访问:

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host: '127.0.0.1',
    port: 3306,
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test',
  },
  userParams: {
    userParam1: '451',
  },
});

在您的应用程序的运行过程中,Knex 实例只应该被初始化一次,该实例将创建一个新的数据库连接池,在您的应用中,永远调用新创建的 Knex 实例即可。

withUserParams

通过在 Knex 实例上面调用 withUserParams 方法,可以创建一个该实例的复本,使用同样的数据库连接:

const knex = require('knex')({
  // Params
});

const knexWithParams = knex.withUserParams({
  customUserParam: 'table1',
});
const customUserParam = knexWithParams.userParams.customUserParam;

debug

在初始化配置对象中,添加 debug: true 属性,则会启用 debugging 功能。

asyncStackTraces

通过添加 asyncStackTraces: true,可以启用实例对所有查询的栈跟踪功能。

pool

Knex 是基于 tarn.js 创建连接池的,该库有一个默认的配置 min: 2, max: 10,若您想修改该配置,可以通过在初始化配置对象中添加 pool 属性来改变它:

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host: '127.0.0.1',
    port: 3306,
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test',
  },
  pool: { min: 0, max: 7 },
});

afterCreate

当一个新的数据库连接被成功创建后, afterCreate 回调函数会被调用。

const knex = require('knex')({
  client: 'pg',
  connection: {
    /*...*/
  },
  pool: {
    afterCreate: function (conn, done) {
      // in this example we use pg driver's connection API
      conn.query('SET timezone="UTC";', function (err) {
        if (err) {
          // first query failed,
          // return error and don't try to make next query
          done(err, conn);
        } else {
          // do the second query...
          conn.query('SELECT set_limit(0.01);', function (err) {
            // if err is not falsy,
            //  connection is discarded from pool
            // if connection aquire was triggered by a
            // query the error is passed to query promise
            done(err, conn);
          });
        }
      });
    },
  },
});

acquireConnectionTimeout

默认情况下,当 Knex 连接数据库超过 60000ms 未成功时,会抛出连接超时异常,可以通过指定 acquireConnectionTimeout 值来修改默认超时时间。

const knex = require('knex')({
  client: 'pg',
  connection: {
    /*...*/
  },
  pool: {
    /*...*/
  },
  acquireConnectionTimeout: 10000,
});

fetchAsString

在使用 Oracle 数据库时,对于 DATENUMBER以及CLOB 类型的数据,可以选择以字符串返回数据:

const knex = require('knex')({
  client: 'oracledb',
  connection: {
    /*...*/
  },
  fetchAsString: ['number', 'clob'],
});

postProcessResponse

查询结果返回给用户前,会调用该钩子函数,这是一个非常有用的钩子,最常见的用处莫过于将查询结果中的属性由 snake_case 转为 camelCase

const knex = require('knex')({
  client: 'mysql',
  // overly simplified snake_case -> camelCase converter
  postProcessResponse: (result, queryContext) => {
    // TODO: add special case for raw results
    // (depends on dialect)
    if (Array.isArray(result)) {
      return result.map((row) => convertToCamel(row));
    } else {
      return convertToCamel(result);
    }
  },
});

wrapIdentifier

Knex 支持将标识符名称自动转换为每种SQL方言的带引号的版本,比如在 PostgreSQL 中, 'Table.columnName as foo' 会被自动转换为 "Table"."columnName" as "foo"

通过 wrapIdentifier 可以重写这种行为,比如,您可以通过该钩子函数来统一将 camelCase 修改为 snake_case 格式。

wrapIdentifier(value, dialectImpl, context): string 函数会将所有标识符遍历为单个值 value

const knex = require('knex')({
  client: 'mysql',
  // overly simplified camelCase -> snake_case converter
  wrapIdentifier: (value, origImpl, queryContext) =>
    origImpl(convertToSnakeCase(value)),
});

log

打开日志。

const knex = require('knex')({
  log: {
    warn(message) {},
    error(message) {},
    deprecate(message) {},
    debug(message) {},
  },
});

Typescript

import { Knex } from 'knex';

declare module 'knex/types/tables' {
  interface User {
    id: number;
    name: string;
    created_at: string;
    updated_at: string;
  }

  interface Tables {
    // This is same as specifying `knex<User>('users')`
    users: User;
    // For more advanced types, you can specify separate type
    // for base model, "insert" type and "update" type.
    // But first: notice that if you choose to use this,
    // the basic typing showed above can be ignored.
    // So, this is like specifying
    //    knex
    //    .insert<{ name: string }>({ name: 'name' })
    //    .into<{ name: string, id: number }>('users')
    users_composite: Knex.CompositeTableType<
      // This interface will be used for return type and
      // `where`, `having` etc where full type is required
      User,
      // Specifying "insert" type will also make sure
      // data matches interface in full. Meaning
      // if interface is `{ a: string, b: string }`,
      // `insert({ a: '' })` will complain about missing fields.
      //
      // For example, this will require only "name" field when inserting
      // and make created_at and updated_at optional.
      // And "id" can't be provided at all.
      // Defaults to "base" type.
      Pick<User, 'name'> & Partial<Pick<User, 'created_at' | 'updated_at'>>,
      // This interface is used for "update()" calls.
      // As opposed to regular specifying interface only once,
      // when specifying separate update interface, user will be
      // required to match it  exactly. So it's recommended to
      // provide partial interfaces for "update". Unless you want to always
      // require some field (e.g., `Partial<User> & { updated_at: string }`
      // will allow updating any field for User but require updated_at to be
      // always provided as well.
      //
      // For example, this wil allow updating all fields except "id".
      // "id" will still be usable for `where` clauses so
      //      knex('users_composite')
      //      .update({ name: 'name2' })
      //      .where('id', 10)`
      // will still work.
      // Defaults to Partial "insert" type
      Partial<Omit<User, 'id'>>
    >;
  }
}
// The trailing `.js` is required by the TypeScript compiler in certain configs:
declare module 'knex/types/tables.js' {
  // <----- Different module path!!!
  interface Tables {
    // ...
  }
}

标签: none

添加新评论