Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cast/uncast STRING and TEXT with non-string type value, and some type definitions #368

Merged
merged 2 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/data_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export abstract class DataType {
/**
* uncast js value into database type with precision
*/
uncast(value: any, _strict?: boolean): any {
uncast(value: any): any {
return value;
}

Expand Down Expand Up @@ -66,6 +66,16 @@ class STRING extends DataType {
return chunks.join(' ');
}

cast(value: any): any {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class Note extends Bone {
  static attributes = {
    meta: TEXT,
  }
}

// fixed:
// logger output: INSERT `notes` (`meta`) VALUES (`name`='bloodborne', `type`='Cthulhu');
// actual SQL: INSERT `notes` (`meta`) VALUES ('[object object]');
const meta = { name: 'bloodborne', type: 'Cthulhu' };
const note = await Note.create({ meta });

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (typeof value === 'object' && value != null) {
return global.JSON.stringify(value);
}
if (value == null) {
return value;
}
return '' + value;
}

uncast(value: string | Raw | null): string | Raw | null {
if (value == null || value instanceof Raw) return value;
return '' + value;
Expand Down Expand Up @@ -305,7 +315,7 @@ class DATE extends DataType {
return this._round(value);
}

uncast(value: null | Raw | string | Date | { toDate: () => Date }, _strict?: boolean): string | Date | Raw | null | undefined {
uncast(value: null | Raw | string | Date | { toDate: () => Date }): string | Date | Raw | null | undefined {
const originValue = value;

// type narrowing doesn't handle `return value` correctly
Expand Down Expand Up @@ -366,7 +376,7 @@ class BOOLEAN extends DataType {
}
}

class TEXT extends DataType {
class TEXT extends STRING {
constructor(length: LENGTH_VARIANTS = LENGTH_VARIANTS.empty) {
if (!Object.values(LENGTH_VARIANTS).includes(length)) {
throw new Error(`invalid text length: ${length}`);
Expand Down
3 changes: 3 additions & 0 deletions src/spell.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,14 @@ export class Spell<T extends typeof AbstractBone, U = InstanceType<T> | Collecti

$where(conditions: WhereConditions<T>): this;
$where(conditions: string, ...values: Literal[]): this;
$where(conditions: Raw, ...values: Literal[]): this;
where(conditions: WhereConditions<T>): Spell<T, U>;
where(conditions: string, ...values: Literal[]): Spell<T, U>;
where(conditions: Raw, ...values: Literal[]): Spell<T, U>;

orWhere(conditions: WhereConditions<T>): Spell<T, U>;
orWhere(conditions: string, ...values: Literal[]): Spell<T, U>;
orWhere(conditions: Raw, ...values: Literal[]): Spell<T, U>;

group(...names: Array<string | Raw>): Spell<T, ResultSet<T>>;

Expand Down
4 changes: 3 additions & 1 deletion src/types/abstract_bone.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export class AbstractBone {
*/
static select<T extends typeof AbstractBone>(this: T, ...names: Array<BoneColumns<T>> | string[]): Spell<T>;
static select<T extends typeof AbstractBone>(this: T, ...names: string[]): Spell<T>;
static select<T extends typeof AbstractBone>(this: T, ...names: Raw[]): Spell<T>;
static select<T extends typeof AbstractBone>(this: T, filter: (name: string) => boolean): Spell<T>;

/**
Expand All @@ -201,7 +202,7 @@ export class AbstractBone {
* Bone.where({ foo: { $eq: 1 } })
*/
static where<T extends typeof AbstractBone>(this: T, whereConditions: WhereConditions<T>): Spell<T, Collection<InstanceType<T>>>;
static where<T extends typeof AbstractBone>(this: T, whereConditions: string, ...values: Literal[]): Spell<T, Collection<InstanceType<T>>>;
static where<T extends typeof AbstractBone>(this: T, whereConditions: string | Raw, ...values: Literal[]): Spell<T, Collection<InstanceType<T>>>;

/**
* Set GROUP fields
Expand All @@ -211,6 +212,7 @@ export class AbstractBone {
*/
static group<T extends typeof AbstractBone>(this: T, ...names: Array<BoneColumns<T>>): Spell<T, ResultSet<T>>;
static group<T extends typeof AbstractBone>(this: T, ...names: Array<string>): Spell<T, ResultSet<T>>;
static group<T extends typeof AbstractBone>(this: T, ...names: Array<Raw>): Spell<T, ResultSet<T>>;

/**
* Set ORDER fields
Expand Down
97 changes: 97 additions & 0 deletions test/integration/suite/data_types.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,103 @@ describe('=> Data types - DATEONLY', function() {
});
});

describe('=> Data types - JSON', function() {
class Note extends Bone {
static attributes = {
meta: JSON,
}
}

before(async () => {
await Note.driver.dropTable('notes');
await Note.sync();
});

it('type casting', async function() {
const meta = { name: 'bloodborne', type: 'Cthulhu' };
const note = await Note.create({ meta });
await note.reload();
assert.deepEqual(note.meta, meta);

const note1 = await Note.findOne({ meta });
assert.deepEqual(note1.meta, meta);

const note2 = await Note.create({ meta: 1 });
assert.equal(note2.meta, 1);
const note3 = await Note.findOne({ meta: 1 });
assert.equal(note3.meta, 1);
assert.equal(note3.id, note2.id);
});
});

describe('=> Data types - TEXT', function() {
class Note extends Bone {
static attributes = {
meta: TEXT,
}
}

before(async () => {
await Note.driver.dropTable('notes');
await Note.sync();
});

it('type casting', async function() {
const meta = { name: 'bloodborne', type: 'Cthulhu' };
const note = await Note.create({ meta });
await note.reload();
assert.equal(note.meta, global.JSON.stringify(meta));

const note1 = await Note.findOne({ meta: global.JSON.stringify(meta) });
assert.equal(note1.meta, global.JSON.stringify(meta));

const note2 = await Note.create({ meta: 1 });
assert.equal(note2.meta, '1');
const note3 = await Note.findOne({ meta: 1 });
assert.equal(note3.meta, '1');
assert.equal(note3.id, note2.id);

const note4 = await Note.create({ meta: 'hardcore' });
assert.equal(note4.meta, 'hardcore');
const note5 = await Note.findOne({ meta: 'hardcore' });
assert.equal(note5.meta, 'hardcore');
});
});

describe('=> Data types - STRING', function() {
class Note extends Bone {
static attributes = {
meta: STRING,
}
}

before(async () => {
await Note.driver.dropTable('notes');
await Note.sync();
});

it('type casting', async function() {
const meta = { name: 'bloodborne', type: 'Cthulhu' };
const note = await Note.create({ meta });
await note.reload();
assert.equal(note.meta, global.JSON.stringify(meta));

const note1 = await Note.findOne({ meta: global.JSON.stringify(meta) });
assert.equal(note1.meta, global.JSON.stringify(meta));

const note2 = await Note.create({ meta: 1 });
assert.equal(note2.meta, '1');
const note3 = await Note.findOne({ meta: 1 });
assert.equal(note3.meta, '1');
assert.equal(note3.id, note2.id);

const note4 = await Note.create({ meta: 'hardcore' });
assert.equal(note4.meta, 'hardcore');
const note5 = await Note.findOne({ meta: 'hardcore' });
assert.equal(note5.meta, 'hardcore');
});
});

describe('=> Data types - complementary', function() {
class Note extends Bone {
static attributes = {
Expand Down
24 changes: 22 additions & 2 deletions test/types/querying.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { strict as assert } from 'assert';
import { Bone, DataTypes, Column, HasMany, connect } from '../..'
import { Bone, DataTypes, Column, HasMany, connect, Raw } from '../..'

describe('=> Querying (TypeScript)', function() {
const { BIGINT, INTEGER, STRING } = DataTypes;
Expand Down Expand Up @@ -103,6 +103,16 @@ describe('=> Querying (TypeScript)', function() {
assert.equal(result.count, 1);
});

it('Bone.group(raw).count()', async function() {
await Post.create({ title: 'Samoa' })
const results = await Post.group(new Raw('title')).count();
assert.ok(Array.isArray(results));
const [result] = results;
assert.ok('title' in result);
assert.equal(result.title, 'Samoa');
assert.equal(result.count, 1);
});

it('Bone.where().count()', async function() {
let count = await Post.where({ title: 'Leah' }).count();
assert.equal(count, 0);
Expand Down Expand Up @@ -135,7 +145,7 @@ describe('=> Querying (TypeScript)', function() {
assert.ok(post);
});

it('Bone.fineOnd({ $and })', async function() {
it('Bone.fineOne({ $and })', async function() {
const post = await Post.findOne({
$and: [
{ authorId: 2, title: { $like: 'Foo%' } },
Expand All @@ -144,5 +154,15 @@ describe('=> Querying (TypeScript)', function() {
});
assert.ok(post);
});

it('Bone.select(Raw)', async function() {
const posts = await Post.select(new Raw('COUNT(author_id) as count'));
assert.equal(posts?.[0].count, 2);
});

it('Bone.where(Raw)', async function() {
const posts = await Post.where(new Raw('author_id = 2'));
assert.equal(posts.length, 2);
});
});
});
22 changes: 22 additions & 0 deletions test/types/spell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,26 @@ describe('=> Spell (TypeScript)', function() {
assert.equal(Post.all.decrement('word_count').toSqlString(), 'UPDATE `articles` SET `word_count` = `word_count` - 1, `gmt_modified` = \'2017-12-12 00:00:00.000\' WHERE `gmt_deleted` IS NULL');
});
});

describe('where/$where', () => {
it('where/$where', () => {
assert.equal(Post.find().where({ id: 1 }).toSqlString(), 'SELECT * FROM `articles` WHERE `id` = 1 AND `gmt_deleted` IS NULL');
assert.equal(Post.find().$where({ id: 1 }).toSqlString(), 'SELECT * FROM `articles` WHERE `id` = 1 AND `gmt_deleted` IS NULL');
});

it('where(raw)/$where(raw)', () => {
assert.equal(Post.find().where(new Raw('id = 1')).toSqlString(), 'SELECT * FROM `articles` WHERE id = 1 AND `gmt_deleted` IS NULL');
assert.equal(Post.find().$where(new Raw('id = 1')).toSqlString(), 'SELECT * FROM `articles` WHERE id = 1 AND `gmt_deleted` IS NULL');
});
});

describe('orWhere', () => {
it('orWhere', () => {
assert.equal(Post.find().where({ id: 1 }).orWhere({ id: 3 }).toSqlString(), 'SELECT * FROM `articles` WHERE (`id` = 1 OR `id` = 3) AND `gmt_deleted` IS NULL');
});

it('orWhere(raw)', () => {
assert.equal(Post.find().where({ id: 1 }).orWhere(new Raw('id = 3')).toSqlString(), 'SELECT * FROM `articles` WHERE (`id` = 1 OR id = 3) AND `gmt_deleted` IS NULL');
});
});
});
2 changes: 1 addition & 1 deletion test/unit/bone.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ describe('=> Bone', function() {
assert.doesNotThrow(function() {
const user = new User();
user.bar = 1;
assert.equal(user.bar, 1);
assert.equal(user.bar, '1');
}, /TypeError: Cannot set property bar/);
});
});
Expand Down