Skip to content

Commit

Permalink
feat: Add support for key prefix lengths
Browse files Browse the repository at this point in the history
  • Loading branch information
nwoltman committed May 9, 2019
1 parent 673fad7 commit 13e185d
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 48 deletions.
41 changes: 35 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1125,9 +1125,9 @@ See the [Column Types](#column-types) section for all possible column types and

### Primary Key

`string|string[]`
`string | string[]`

The table’s primary key can be defined with the `primaryKey` property:
The table’s primary key can be defined with the `primaryKey` property.

```js
{
Expand All @@ -1151,6 +1151,17 @@ An array can be used to define a multi-column primary key.
}
```

Primary keys for string columns may include a key [prefix length](#prefix-lengths).

```js
{
columns: {
id: pool.ColTypes.varchar(100).unsigned().notNull(),
},
primaryKey: 'id(20)'
}
```

### Keys

Keys can be defined with the `keys` property, which is an array of [`KeyTypes`](#key-types):
Expand Down Expand Up @@ -1395,10 +1406,10 @@ Compatible types:

[`mysql.KeyTypes`](#module_mysql-plus..KeyTypes) and [`pool.KeyTypes`](#PoolPlus+KeyTypes) both expose the following methods for defining table keys:

+ `index(columnName [, ...otherColumns])` - Creates a regular [index](https://dev.mysql.com/doc/en/create-index.html)
+ `uniqueIndex(columnName [, ...otherColumns])` - Creates a [unique index](https://dev.mysql.com/doc/en/create-index.html#create-index-unique)
+ `index(keyPart [, ...otherKeyParts])` - Creates a regular [index](https://dev.mysql.com/doc/en/create-index.html)
+ `uniqueIndex(keyPart [, ...otherKeyParts])` - Creates a [unique index](https://dev.mysql.com/doc/en/create-index.html#create-index-unique)
+ `spatialIndex(columnName)` - Creates a [spatial index](https://dev.mysql.com/doc/en/create-index.html#create-index-spatial)
+ `fulltextIndex(columnName)` - Creates a [fulltext index](https://dev.mysql.com/doc/en/innodb-fulltext-index.html)
+ `fulltextIndex(columnName, [...otherColumns])` - Creates a [fulltext index](https://dev.mysql.com/doc/en/innodb-fulltext-index.html)
+ `foreignKey(columnName [, ...otherColumns])` - Creates a [foreign key constraint](https://dev.mysql.com/doc/en/create-table-foreign-keys.html)

**Example:**
Expand Down Expand Up @@ -1473,4 +1484,22 @@ Indexes required for the example above:
KeyTypes.index('thingOne', 'thingTwo'),
]
}
```
```

### Prefix Lengths

`PRIMARY`, `INDEX`, and `UNIQUE` keys on `char`, `varchar`, `binary`, `varbinary`, `blob`, and `text` columns may include a [key prefix length](https://dev.mysql.com/doc/en/create-index.html#create-index-column-prefixes).

```js
{
columns: {
id: ColTypes.char(50).notNull(),
uid: ColTypes.varchar(100).notNull(),
description: ColTypes.text(),
},
primaryKey: 'id(10)',
keys: [
KeyTypes.uniqueIndex('uid(30)'),
KeyTypes.index('description(50)'),
]
```
41 changes: 35 additions & 6 deletions jsdoc2md/README.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ See the [Column Types](#column-types) section for all possible column types and

### Primary Key

`string|string[]`
`string | string[]`

The table’s primary key can be defined with the `primaryKey` property:
The table’s primary key can be defined with the `primaryKey` property.

```js
{
Expand All @@ -192,6 +192,17 @@ An array can be used to define a multi-column primary key.
}
```

Primary keys for string columns may include a key [prefix length](#prefix-lengths).

```js
{
columns: {
id: pool.ColTypes.varchar(100).unsigned().notNull(),
},
primaryKey: 'id(20)'
}
```

### Keys

Keys can be defined with the `keys` property, which is an array of [`KeyTypes`](#key-types):
Expand Down Expand Up @@ -436,10 +447,10 @@ Compatible types:

[`mysql.KeyTypes`](#module_mysql-plus..KeyTypes) and [`pool.KeyTypes`](#PoolPlus+KeyTypes) both expose the following methods for defining table keys:

+ `index(columnName [, ...otherColumns])` - Creates a regular [index](https://dev.mysql.com/doc/en/create-index.html)
+ `uniqueIndex(columnName [, ...otherColumns])` - Creates a [unique index](https://dev.mysql.com/doc/en/create-index.html#create-index-unique)
+ `index(keyPart [, ...otherKeyParts])` - Creates a regular [index](https://dev.mysql.com/doc/en/create-index.html)
+ `uniqueIndex(keyPart [, ...otherKeyParts])` - Creates a [unique index](https://dev.mysql.com/doc/en/create-index.html#create-index-unique)
+ `spatialIndex(columnName)` - Creates a [spatial index](https://dev.mysql.com/doc/en/create-index.html#create-index-spatial)
+ `fulltextIndex(columnName)` - Creates a [fulltext index](https://dev.mysql.com/doc/en/innodb-fulltext-index.html)
+ `fulltextIndex(columnName, [...otherColumns])` - Creates a [fulltext index](https://dev.mysql.com/doc/en/innodb-fulltext-index.html)
+ `foreignKey(columnName [, ...otherColumns])` - Creates a [foreign key constraint](https://dev.mysql.com/doc/en/create-table-foreign-keys.html)

**Example:**
Expand Down Expand Up @@ -514,4 +525,22 @@ Indexes required for the example above:
KeyTypes.index('thingOne', 'thingTwo'),
]
}
```
```

### Prefix Lengths

`PRIMARY`, `INDEX`, and `UNIQUE` keys on `char`, `varchar`, `binary`, `varbinary`, `blob`, and `text` columns may include a [key prefix length](https://dev.mysql.com/doc/en/create-index.html#create-index-column-prefixes).

```js
{
columns: {
id: ColTypes.char(50).notNull(),
uid: ColTypes.varchar(100).notNull(),
description: ColTypes.text(),
},
primaryKey: 'id(10)',
keys: [
KeyTypes.uniqueIndex('uid(30)'),
KeyTypes.index('description(50)'),
]
```
12 changes: 7 additions & 5 deletions lib/KeyDefinitions/IndexKeyDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

const arraysEqual = require('../utils/arraysEqual');
const {escapeId} = require('mysql/lib/protocol/SqlString');
const parseKeyParts = require('../utils/parseKeyParts');

class IndexKeyDefinition {
constructor(type, namePrefix, columns) {
this.$name = namePrefix + '_' + columns.join('_');
constructor(type, namePrefix, keyParts) {
const {columnNames, formattedKeyParts} = parseKeyParts(keyParts);
this.$name = namePrefix + '_' + columnNames.join('_');
this._type = type;
this._columns = columns;
this._keyParts = formattedKeyParts;
}

name(name) {
Expand All @@ -18,11 +20,11 @@ class IndexKeyDefinition {
$equals(otherKey) {
return this._type === otherKey._type &&
this.$name === otherKey.$name &&
arraysEqual(this._columns, otherKey._columns);
arraysEqual(this._keyParts, otherKey._keyParts);
}

$toSQL() {
return `${this._type} ${escapeId(this.$name)} (${escapeId(this._columns)})`;
return `${this._type} ${escapeId(this.$name)} (${this._keyParts.join(', ')})`;
}
}

Expand Down
12 changes: 7 additions & 5 deletions lib/KeyDefinitions/PrimaryKeyDefinition.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
'use strict';

const arraysEqual = require('../utils/arraysEqual');
const {escapeId} = require('mysql/lib/protocol/SqlString');
const parseKeyParts = require('../utils/parseKeyParts');

class PrimaryKeyDefinition {
constructor(columns) {
this._columns = columns;
constructor(keyParts) {
const {columnNames, formattedKeyParts} = parseKeyParts(keyParts);
this.$columnNames = columnNames;
this._keyParts = formattedKeyParts;
}

$equals(otherKey) {
return arraysEqual(this._columns, otherKey._columns);
return arraysEqual(this._keyParts, otherKey._keyParts);
}

$toSQL() {
return `PRIMARY KEY (${escapeId(this._columns)})`;
return `PRIMARY KEY (${this._keyParts.join(', ')})`;
}
}

Expand Down
33 changes: 14 additions & 19 deletions lib/TableDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ class TableDefinition {
`CHANGE COLUMN ${pool.escapeId(oldColumnName)} ${pool.escapeId(columnName)} ` +
newColumnDefinition.$toSQL() + position
));

renamedColumns.push(oldColumnName);
} else if (!newColumnDefinition.$equals(oldColumnDefinition, oldSchema)) {
operations.push(Operation.create(
Expand Down Expand Up @@ -390,14 +389,14 @@ function createInternalSchema(schema, tableName) {
}
}

let primaryKey = null;
let columnPK = null;

// Extract keys defined in the columns
for (const columnName in columns) {
const column = columns[columnName];

if (column.$primaryKey) {
primaryKey = columnName;
columnPK = columnName;
}
if (column.$unique) {
const key = KeyDefinitions.uniqueIndex(columnName);
Expand All @@ -417,24 +416,20 @@ function createInternalSchema(schema, tableName) {
}
}

if (schema.primaryKey) {
let primaryKey = null;

if (schema.primaryKey) { // The primaryKey in the schema takes precedence over one defined by a column
if (typeof schema.primaryKey === 'string') {
primaryKey = new PrimaryKeyDefinition([schema.primaryKey]);
} else { // schema.primaryKey is an Array
primaryKey = new PrimaryKeyDefinition(schema.primaryKey);
}
// Make sure primary key columns are not null
if (Array.isArray(schema.primaryKey)) {
for (const colName of schema.primaryKey) {
columns[colName].notNull();
}
} else {
columns[schema.primaryKey].notNull();
for (const colName of primaryKey.$columnNames) {
columns[colName].notNull();
}

// The primaryKey in the schema takes precedence over one defined by a column
primaryKey = schema.primaryKey;
}

if (typeof primaryKey === 'string') {
primaryKey = new PrimaryKeyDefinition([primaryKey]);
} else if (Array.isArray(primaryKey)) {
primaryKey = new PrimaryKeyDefinition(primaryKey);
} else if (columnPK !== null) {
primaryKey = new PrimaryKeyDefinition([columnPK]);
}

return Object.assign({}, schema, {primaryKey, indexKeys, foreignKeys});
Expand Down
12 changes: 6 additions & 6 deletions lib/sqlToSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,20 @@ function generateColumnsSchema(createDefinitions) {
}

function columnsSQLToSchema(sql) {
return sql.replace(/`|\s/g, '').split(',');
return sql.replace(/`/g, '').split(/,\s*/);
}

function generatePrimaryKeySchema(keySQL) {
const pkMatch = /^\s*PRIMARY KEY \((.*?)\)/.exec(keySQL);
const pkMatch = /^\s*PRIMARY KEY \((.*)\)/.exec(keySQL);
return pkMatch === null
? null
: new PrimaryKeyDefinition(columnsSQLToSchema(pkMatch[1]));
}

const rgxUniqueKey = /^\s*UNIQUE KEY `(\w+)` \((.*?)\)/;
const rgxIndexKey = /^\s*KEY `(\w+)` \((.*?)\)/;
const rgxSpatialKey = /^\s*SPATIAL KEY `(\w+)` \((.*?)\)/;
const rgxFulltextKey = /^\s*FULLTEXT KEY `(\w+)` \((.*?)\)/;
const rgxUniqueKey = /^\s*UNIQUE KEY `(\w+)` \((.*)\)/;
const rgxIndexKey = /^\s*KEY `(\w+)` \((.*)\)/;
const rgxSpatialKey = /^\s*SPATIAL KEY `(\w+)` \((.*)\)/;
const rgxFulltextKey = /^\s*FULLTEXT KEY `(\w+)` \((.*)\)/;
const rgxForeignKey =
/\s*CONSTRAINT `(\w+)` FOREIGN KEY \(`(.*?)`\) REFERENCES `(\w+)` \(`(.*?)`\)(?: ON DELETE (RESTRICT|CASCADE|SET NULL|NO ACTION))?(?: ON UPDATE (RESTRICT|CASCADE|SET NULL|NO ACTION))?/;

Expand Down
44 changes: 44 additions & 0 deletions lib/utils/parseKeyParts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

const {escapeId} = require('mysql/lib/protocol/SqlString');

function parseKeyParts(keyParts) {
const columnNames = [];
const formattedKeyParts = [];

for (let i = 0; i < keyParts.length; i++) {
const parsedKeyPart = parseKeyPart(keyParts[i]);

columnNames.push(parsedKeyPart.column);
formattedKeyParts.push(formatKeyPart(parsedKeyPart));
}

return {columnNames, formattedKeyParts};
}

const rgxKeyPart = /^(\w+)(?:\s*\((\d+)\))?(?:\s+(ASC|DESC))?/i;

function parseKeyPart(keyPart) {
const match = rgxKeyPart.exec(keyPart);

if (match === null) {
throw new Error('Invalid key part: ' + keyPart);
}

return {
column: match[1],
length: match[2],
};
}

function formatKeyPart(keyPart) {
let sql = escapeId(keyPart.column);

if (keyPart.length !== undefined) {
sql += `(${keyPart.length})`;
}

return sql;
}

module.exports = parseKeyParts;
Loading

0 comments on commit 13e185d

Please sign in to comment.