Skip to content

Commit

Permalink
fix: subclassing data type in dialects (#145)
Browse files Browse the repository at this point in the history
* fix: subclassing data type in dialects

exports of the abstract data types should not be wrapped with invokable by default, which made subclassing like `class Postgres_DATE extends DataTypes.DATE {}` not working as expected because `DataTypes.DATE` was actually a Proxy instance.

This fix defers the invokable wrapping and adds more test cases regarding data types in dialects.

* Update test/unit/drivers/postgres/attribute.test.js

Co-authored-by: JimmyDaddy <[email protected]>

* test: merge test cases

Co-authored-by: JimmyDaddy <[email protected]>
  • Loading branch information
cyjake and JimmyDaddy authored Jul 21, 2021
1 parent 221352c commit 6e2f51b
Show file tree
Hide file tree
Showing 15 changed files with 146 additions and 51 deletions.
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const { Bone, connect } = require('leoric')

// define model
class Post extends Bone {
static describe() {
static initialize() {
this.belongsTo('author', { Model: 'User' })
this.hasMany('comments')
}
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const Logger = require('./src/drivers/abstract/logger');
const Spell = require('./src/spell');
const Bone = require('./src/bone');
const Collection = require('./src/collection');
const DataTypes = require('./src/data_types');
const { invokable: DataTypes } = require('./src/data_types');
const { findDriver } = require('./src/drivers');
const migrations = require('./src/migrations');
const sequelize = require('./src/adapters/sequelize');
Expand Down
21 changes: 15 additions & 6 deletions src/data_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class JSONB extends DataType {
}
}

const DataTypes = [
const DataTypes = {
STRING,
INTEGER,
BIGINT,
Expand All @@ -212,10 +212,19 @@ const DataTypes = [
BLOB,
JSON,
JSONB,
];

for (const klass of DataTypes) {
DataType[klass.name] = invokable(klass);
}
};

Object.assign(DataType, DataTypes);
Object.defineProperty(DataType, 'invokable', {
get() {
return new Proxy(this, {
get(target, p) {
const value = target[p];
if (DataTypes.hasOwnProperty(p)) return invokable(value);
return value;
}
});
},
});

module.exports = DataType;
17 changes: 6 additions & 11 deletions src/drivers/abstract/attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,16 @@

const debug = require('debug')('leoric');

const DataTypes = require('../../data_types');
const { snakeCase } = require('../../utils/string');

/**
* Find the corresponding JavaScript type of the type in database.
* @param {string} dataType
*/
function findJsType(type, defaultDataType) {
// MySQL stores BOOLEAN as TINYINT(1)
if (type instanceof DataTypes.BOOLEAN) {
return Boolean;
}

const dataType = defaultDataType || type.dataType;

function findJsType(dataType) {
switch (dataType.toLowerCase().split('(')[0]) {
case 'boolean':
return Boolean;
case 'bigint':
case 'mediumint':
case 'smallint':
Expand Down Expand Up @@ -81,6 +75,7 @@ class Attribute {
* @param {boolean} opts.underscored
*/
constructor(name, params, { underscored } = {}) {
const { DataTypes } = this.constructor;
if (params instanceof Attribute) return params;
const columnName = underscored === false ? name : snakeCase(name);
if (params == null) return Object.assign(this, { name, columnName });
Expand All @@ -90,7 +85,7 @@ class Attribute {
if (typeof params === 'function' || params instanceof DataTypes) {
params = { type: params };
}
const type = createType(this.constructor.DataTypes, params);
const type = createType(DataTypes, params);
const dataType = params.dataType || type.dataType;
Object.assign(this, {
name,
Expand All @@ -102,7 +97,7 @@ class Attribute {
...params,
type,
dataType,
jsType: findJsType(type, dataType),
jsType: type instanceof DataTypes.BOOLEAN ? Boolean : findJsType(dataType),
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/drivers/abstract/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ module.exports = class AbstractDriver {
* @param {Object|Date|string|Boolean} type
* @returns {Object|Date|string|boolean}
*/
cast(value, type) {
cast(value, jsType) {
if (value == null) return value;

switch (type) {
switch (jsType) {
case JSON:
if (!value) return null;
// type === JSONB
Expand Down
4 changes: 1 addition & 3 deletions src/drivers/postgres/attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ class PostgresAttribute extends Attribute {
super(name, params, opts);
}

static get DataTypes() {
return DataTypes;
}
static DataTypes = DataTypes.invokable;

toSqlString() {
const {
Expand Down
2 changes: 1 addition & 1 deletion src/drivers/postgres/data_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Postgres_DATE extends DataTypes.DATE {

toSqlString() {
const { timezone } = this;
if (timezone) return `${super.toSqlString()} WITH TIME ZONE}`;
if (timezone) return `${super.toSqlString()} WITH TIME ZONE`;
return `${super.toSqlString()} WITHOUT TIME ZONE`;
}
}
Expand Down
36 changes: 19 additions & 17 deletions test/integration/suite/data_types.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ const { INTEGER, STRING, DATE, TEXT, BOOLEAN, JSON, JSONB } = DataTypes;


describe('=> Data types', () => {
class Note extends Bone {};
before(async () => {
await Note.driver.dropTable('notes');
Note.init({
class Note extends Bone {
static attributes = {
id: { type: INTEGER, primaryKey: true },
title: STRING,
body: TEXT,
isPrivate: BOOLEAN,
createdAt: DATE,
updatedAt: DATE,
publishedAt: DATE(6),
});
}
};
before(async () => {
await Note.driver.dropTable('notes');
await Note.sync();
});

Expand Down Expand Up @@ -65,18 +66,19 @@ describe('=> Data types', () => {
describe('=> Data types - JSON', () => {

it('=> init', async () => {
class Note extends Bone {};
Note.init({
id: { type: INTEGER, primaryKey: true },
title: STRING,
body: TEXT,
isPrivate: BOOLEAN,
createdAt: DATE,
updatedAt: DATE,
publishedAt: DATE(6),
meta: JSON,
metab: JSONB,
});
class Note extends Bone {
static attributes = {
id: { type: INTEGER, primaryKey: true },
title: STRING,
body: TEXT,
isPrivate: BOOLEAN,
createdAt: DATE,
updatedAt: DATE,
publishedAt: DATE(6),
meta: JSON,
metab: JSONB,
}
}
await Note.sync();
await Note.create({ title: 'Leah', meta: { foo: 1, baz: 'baz' }, metab: { foo: 2, baz: 'baz1' } });
const foundNote = await Note.first;
Expand Down
19 changes: 14 additions & 5 deletions test/unit/data_types.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

const assert = require('assert').strict;
const DataTypes = require('../../src/data_types');
const {
STRING, BOOLEAN, DATE, INTEGER, BIGINT, TEXT, JSON, JSONB
} = DataTypes;

describe('=> Data Types', () => {
const {
STRING, BOOLEAN, DATE, INTEGER, BIGINT, TEXT, JSON, JSONB
} = DataTypes;

it('STRING', () => {
assert.equal(new STRING().dataType, 'varchar');
assert.equal(new STRING().toSqlString(), 'VARCHAR(255)');
Expand Down Expand Up @@ -46,8 +47,16 @@ describe('=> Data Types', () => {
});
});

describe('findType()', () => {
describe('DataTypes.findType()', () => {
const { TEXT } = DataTypes;
it('longtext => TEXT', () => {
assert.strictEqual(DataTypes.findType('longtext'), TEXT);
assert.equal(DataTypes.findType('longtext'), TEXT);
});
});

describe('DataTypes.invokable', function() {
const { STRING } = DataTypes.invokable;
it('should wrap data types to support flexible invoking', async function() {
assert.equal(STRING(255).toSqlString(), 'VARCHAR(255)');
});
});
15 changes: 15 additions & 0 deletions test/unit/drivers/mysql/attribute.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const assert = require('assert').strict;
const Attribute = require('../../../../src/drivers/mysql/attribute');
const { BOOLEAN } = Attribute.DataTypes;

describe('=> Attribute (mysql)', function() {
it('should support TINYINT(1)', async function() {
const attribute= new Attribute('has_image', {
type: BOOLEAN,
defaultValue: false,
});
assert.equal(attribute.toSqlString(), '`has_image` TINYINT(1) DEFAULT false');
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const assert = require('assert').strict;
const MysqlDriver = require('../../../src/drivers/mysql');
const MysqlDriver = require('../../../../src/drivers/mysql');

const database = 'leoric';
const options = {
Expand Down
44 changes: 44 additions & 0 deletions test/unit/drivers/postgres/attribute.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

const assert = require('assert').strict;
const Attribute = require('../../../../src/drivers/postgres/attribute');
const { BIGINT, INTEGER, DATE } = Attribute.DataTypes;

describe('=> Attribute (postgres)', function() {
it('should support BIGSERIAL', async function() {
const attribute= new Attribute('id', {
type: BIGINT,
primaryKey: true,
});
assert.equal(attribute.toSqlString(), '"id" BIGSERIAL PRIMARY KEY');
});

it('should support SERIAL', async function() {
const attribute= new Attribute('id', {
type: INTEGER,
primaryKey: true,
});
assert.equal(attribute.toSqlString(), '"id" SERIAL PRIMARY KEY');
});

it('should support WITH TIME ZONE', async function() {
const attribute= new Attribute('createdAt', {
type: DATE,
});
assert.equal(attribute.toSqlString(), '"created_at" TIMESTAMP WITH TIME ZONE');
});

it('should support WITHOUT TIME ZONE', async function() {
const attribute = new Attribute('createdAt', {
type: DATE(null, false),
});
assert.equal(attribute.toSqlString(), '"created_at" TIMESTAMP WITHOUT TIME ZONE');
});

it('should support DATE(precision)', async function() {
const attribute= new Attribute('updated_at', {
type: DATE(6),
});
assert.equal(attribute.toSqlString(), '"updated_at" TIMESTAMP(6) WITH TIME ZONE');
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const assert = require('assert').strict;
const PostgresDriver = require('../../../src/drivers/postgres');
const PostgresDriver = require('../../../../src/drivers/postgres');

const database = 'leoric';
const options = {
Expand Down
22 changes: 22 additions & 0 deletions test/unit/drivers/sqlite/attribute.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const assert = require('assert').strict;
const Attribute = require('../../../../src/drivers/sqlite/attribute');
const { BIGINT, DATE } = Attribute.DataTypes;

describe('=> Attribute (sqlite)', function() {
it('should support DATETIME', async function() {
const attribute= new Attribute('createdAt', {
type: DATE,
});
assert.equal(attribute.toSqlString(), '"created_at" DATETIME');
});

it('should support BIGINT', async function() {
const attribute= new Attribute('id', {
type: BIGINT,
primaryKey: true,
});
assert.equal(attribute.toSqlString(), '"id" INTEGER PRIMARY KEY');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
const assert = require('assert').strict;
const strftime = require('strftime');

const { heresql } = require('../../../src/utils/string');
const SqliteDriver = require('../../../src/drivers/sqlite');
const { heresql } = require('../../../../src/utils/string');
const SqliteDriver = require('../../../../src/drivers/sqlite');

const { INTEGER, BIGINT, STRING, DATE, BOOLEAN } = SqliteDriver.prototype.DataTypes;

Expand Down Expand Up @@ -64,6 +64,7 @@ describe('=> SQLite driver', () => {
await driver.createTable('notes', {
id: { type: BIGINT, primaryKey: true, autoIncrement: true },
public: { type: INTEGER },
has_image: { type: BOOLEAN, defaultValue: false },
});
});

Expand Down

0 comments on commit 6e2f51b

Please sign in to comment.