Problem with insert
Rwanito opened this issue · comments
Issue type:
[X] Question
[ ] Bug report
[ ] Feature request
[ ] Documentation issue
Database system/driver:
[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift
typed-knex version:
[X latest
[ ] @next
[ ] 0.x.x
(or put your version here)
Knex.js version:
Latest
Steps to reproduce or a small repository showing the problem:
Hi !
I want to make a simple insertion with two models.
import { Column, Table } from '@wwwouter/typed-knex';
import { Email, UUID } from '../../ts/types/base.type';
@Table('user')
class UserDB {
@Column({ primary: true })
public id!: UUID;
@Column()
public firstname!: string;
@Column()
public lastname!: string;
@Column()
public email!: Email;
@Column()
public birthdate!: Date;
@Column()
public gender: 'M' | 'F' | 'N' = 'N';
@Column()
public created_at!: Date;
@Column()
public updated_at!: Date;
@Column()
public termsofuse: boolean = true;
@Column()
public active: boolean = true;
}
export default UserDB;
import { Column, Table } from '@wwwouter/typed-knex';
import User from './User.db.model';
import { Bcrypt, UUID } from '../../ts/types/base.type';
@Table('password')
class PasswordDB {
@Column({ primary: true })
public id!: UUID;
@Column()
public password!: Bcrypt;
@Column({ name: 'user_id' })
public user!: User;
@Column()
public created_at!: Date;
@Column()
public updated_at!: Date;
}
export default PasswordDB;
I just run :
await db.query(UserDB).insertItem(userDB);
await db.query(GroupDB).insertItem(groupDB);
userDB and groupDB are instances of UserDB and GroupDB. They are full and there is no misfiled attributes.
And I got :
Error: insert into `group` (`group_name`, `user_id`) values (0, {"gender":"N","termsofuse":true,"active":true,"id":"6dc9f9f9-ae34-4eef-8a33-6f514ec05c6e","firstname":"string","lastname":"string","email":"user@example.com","birthdate":"2020-10-08T13:51:50.489Z","created_at":"2020-10-08 16:11:18","updated_at":"2020-10-08 16:11:18"}) - Column count doesn't match value count at row 1
at Packet.asError (/........node_modules/mysql2/lib/packets/packet.js:712:17)
at Query.execute (......../node_modules/mysql2/lib/commands/command.js:28:26)
at Connection.handlePacket (/.........node_modules/mysql2/lib/connection.js:425:32)
at PacketParser.onPacket (/h........node_modules/mysql2/lib/connection.js:75:12)
at PacketParser.executeStart (/.........../node_modules/mysql2/lib/packet_parser.js:75:16)
at Socket.<anonymous> (/............./node_modules/mysql2/lib/connection.js:82:25)
at Socket.emit (events.js:314:20)
at Socket.EventEmitter.emit (domain.js:486:12)
at addChunk (_stream_readable.js:307:12)
at readableAddChunk (_stream_readable.js:282:9)
What can I do, to avoid to put the user object instead of just user.id ?
Thanks for your work !
Can you share GroupDB
and the code where you set the properties of groupDB
?
Here my GroupDB
import { Column, Table } from '@wwwouter/typed-knex';
import User from './User.db.model';
export enum TypeGroup {
Student,
Teacher,
SuperTeacher,
Admin
}
@Table('group')
class GroupDB {
@Column({ name: 'user_id' })
public user!: User;
@Column()
public group_name!: TypeGroup;
}
export default GroupDB;
And, here how I create my GroupDB
import GroupDB, { TypeGroup } from './db/Group.db.model';
import User from './User.model';
class Group implements GroupDB {
public user: User;
public group_name: TypeGroup;
public constructor(groupCreationParams: { user: User, group_name: TypeGroup }) {
this.user = groupCreationParams.user;
this.group_name = groupCreationParams.group_name;
}
// /**
// * Recupère l'instance GroupDB pour permettre une gestion en base de données.
// */
// private getGroupDB(): GroupDB {
// /**
// * Possibilité de destructurer l'objet puisque User implemente GroupDB.
// */
// return { ...this } as GroupDB;
// }
}
And How I set User :
import { v4 as uuidv4 } from 'uuid';
import { Email, UUID } from '../ts/types/base.type';
import UserDB from './db/User.db.model';
import db from '../libs/db.lib';
import { UserCreationParams } from '../ts/types/params.type';
class User implements UserDB {
public id: UUID;
public firstname: string;
public lastname: string;
public email: Email;
public birthdate: Date;
public gender: 'M' | 'F' | 'N' = 'N';
public created_at!: Date;
public updated_at!: Date;
public termsofuse: boolean = true;
public active: boolean = true;
public constructor(userCreationParams: UserCreationParams) {
this.id = uuidv4();
this.firstname = userCreationParams.firstname;
this.lastname = userCreationParams.lastname;
this.email = userCreationParams.email;
this.birthdate = userCreationParams.birthdate;
this.gender = userCreationParams.gender;
}
// /**
// * Recupère l'instance UserDB pour permettre une gestion en base de données.
// */
// private getUserDB(): UserDB {
// /**
// * Possibilité de destructurer l'objet puisque User implemente UserDB.
// */
// return { ...this } as UserDB;
// }
// public static async getUserById(id: UUID): Promise<User> {
// return db.query(UserDB).where((i) => i.id, id).getFirst()
// .then((userDB) => new User(userDB));
// }
// public async insertUser(): Promise<User> {
// return db.query(UserDB).insertItem(this.getUserDB())
// .then(() => this);
// }
}
export default User;
To set UserDB or GroupDB, I just do : { ...this } as GroupDB or UserDB, because my class implements the model.
It is okay to do like that ?
So to insert GroupDB or UserDB, I do db.query(UserDB).insertItem(myUserFromUserClass.getUserDB())
Thanks you for your reply !
Looking at the error message, it seems like you're inserting a UserDB object into a .query<GroupDB>
query.
Personally I stopped using a class like User
some time ago. Storing the user data and providing access to the database in one class breaks the single responsibility principle. Now I always use a repository for data access, or just use typed-knex directly in a controller..
Hi !
I write UUID instead of UserDB because it doesn't work.
However, it's the good class I have.
I've tried with only my interface and I have the same result.
You right, I will write a specific user model !
Thanks a lot !
Hi !
I have always the same issue.
In my SQL, I use knex to migrate.
await knex.schema.createTable(tableUser, (table) => {
table.uuid('id').notNullable().primary();
table.string('firstname').notNullable();
table.string('lastname').notNullable();
table.string('email').notNullable().unique();
table.date('birthdate').notNullable();
table.string('gender', 1).notNullable().defaultTo('N');
table.boolean('active').notNullable().defaultTo(true);
table.boolean('termsofuse').notNullable().defaultTo(true);
table
.timestamp('created_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP'));
table
.timestamp('updated_at')
.notNullable()
.defaultTo(
knex.raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
);
});
/**
* Création de la table group et group_user
*/
await knex.schema.createTable(tableGroup, (table) => {
// Table
table.integer('group_name').notNullable();
table.uuid('user_id').notNullable();
// Clefs
table.foreign('user_id').references('user.id');
import { Column, Table } from '@wwwouter/typed-knex';
import { Email, GenderType, UUID } from '../../types/base.type';
@Table('user')
class UserInDB {
@Column({ primary: true })
id!: UUID;
@Column()
firstname!: string;
@Column()
lastname!: string;
@Column()
email!: Email;
@Column()
birthdate!: Date;
@Column()
gender!: GenderType;
@Column()
created_at!: Date;
@Column()
updated_at!: Date;
@Column()
termsofuse!: boolean;
@Column()
active!: boolean;
}
export default UserInDB;
and
import { Column, Table } from '@wwwouter/typed-knex';
import { TypeGroup } from '../../types/base.type';
import UserInDB from './user.class';
/**
* Définition de Group dans la base de données.
*/
@Table('group')
class GroupInDB {
@Column({ name: 'user_id' })
user!: UserInDB;
@Column({ primary: true })
group_name!: TypeGroup;
}
export default GroupInDB;
When I try a simple insert :
const user = new UserInDB();
Object.assign(user, {
id: 'test',
active: true,
birthdate: new Date(),
created_at: new Date(),
email: 'test@test.com',
firstname: 'ok',
gender: GenderType.MALE,
lastname: 'ok',
termsofuse: true,
updated_at: new Date(),
});
const group = new GroupInDB();
Object.assign(group, {
user,
group_name: TypeGroup.Student,
});
await typedKnex.query(UserInDB).insertItem(user);
await typedKnex.query(GroupInDB).insertItem(group);
I have
insert into `group` (`group_name`, `user_id`) values (0, {"id":"test","active":true,"birthdate":"2020-10-17T10:56:34.425Z","created_at":"2020-10-17T10:56:34.425Z","email":"test@test.com","firstname":"ok","gender":"M","lastname":"ok","termsofuse":true,"updated_at":"2020-10-17T10:56:34.425Z"}) - Column count doesn't match value count at row 1
Error: insert into `group` (`group_name`, `user_id`) values (0, {"id":"test","active":true,"birthdate":"2020-10-17T10:56:34.425Z","created_at":"2020-10-17T10:56:34.425Z","email":"test@test.com","firstname":"ok","gender":"M","lastname":"ok","termsofuse":true,"updated_at":"2020-10-17T10:56:34.425Z"}) - Column count doesn't match value count at row 1
at Packet.asError (/home/erwan//-api/node_modules/mysql2/lib/packets/packet.js:712:17)
at Query.execute (/home/erwan//-api/node_modules/mysql2/lib/commands/command.js:28:26)
at Connection.handlePacket (/home/erwan//-api/node_modules/mysql2/lib/connection.js:425:32)
at PacketParser.onPacket (/home/erwan//-api/node_modules/mysql2/lib/connection.js:75:12)
at PacketParser.executeStart (/home/erwan//-api/node_modules/mysql2/lib/packet_parser.js:75:16)
at Socket.<anonymous> (/home/erwan//-api/node_modules/mysql2/lib/connection.js:82:25)
at Socket.emit (events.js:314:20)
at Socket.EventEmitter.emit (domain.js:486:12)
at addChunk (_stream_readable.js:307:12)
at readableAddChunk (_stream_readable.js:282:9)
User is inserted, but not Group.
Did I miss anything?
Thanks you !
Hi,
If you'd log the group object, you should see what's going wrong.
console.log(group)
await typedKnex.query(GroupInDB).insertItem(group);
group
should only contain the fields you want to insert in the database, so user_id
and group_name
.
Right now the Object.assign
puts too much data in the group
object.
I think something like this should work:
@Table('group')
class GroupInDB {
@Column()
user_id!: string; // always add the real column
@Column({ name: 'user_id' })
user!: UserInDB;
@Column({ primary: true })
group_name!: number; // I would keep this type as close to the database type as possible
}
const user = new UserInDB();
Object.assign(user, {
id: 'test',
active: true,
birthdate: new Date(),
created_at: new Date(),
email: 'test@test.com',
firstname: 'ok',
gender: GenderType.MALE,
lastname: 'ok',
termsofuse: true,
updated_at: new Date(),
});
const group = new GroupInDB();
Object.assign(group, {
user_id: user.id,
group_name: TypeGroup.Student,
});
await typedKnex.query(UserInDB).insertItem(user);
await typedKnex.query(GroupInDB).insertItem(group);
Thanks for you reply !
It works !
But, now I have another problem.
I want to make a join.
Password contains the id from user. I want to get results joined with this id.
In UserDAO
/**
* Renvoie vrai si le duo Email+Password correspond.
* @param {LoginParams} loginParams
*/
public static async checkLogin(_loginParams: LoginParams): Promise<boolean> {
const test = await typedKnex.query(PasswordInDB).innerJoinColumn((i) => i.user_id).getMany();
console.log(test);
return true;
}
The error is :
TypeError: Cannot read property 'tableName' of undefined at TypedQueryBuilder.joinColumn (/home/erwan/astronef/astronef-api/node_modules/@wwwouter/typed-knex/src/typedKnex.ts:1876:68) at TypedQueryBuilder.innerJoinColumn (/home/erwan/astronef/astronef-api/node_modules/@wwwouter/typed-knex/src/typedKnex.ts:1130:21) at Function.<anonymous> (/home/erwan/astronef/astronef-api/src/models/userDAO.model.ts:48:54)
PasswordInDB
@Table('password')
class PasswordInDB {
@Column({ primary: true })
id!: string;
@Column()
password!: string;
@Column()
user_id!: string;
@Column({ name: 'user_id' })
user!: UserInDB;
@Column()
created_at!: Date;
@Column()
updated_at!: Date;
}
And UserInDB
@Table('user')
class UserInDB {
@Column({ primary: true })
id!: string;
@Column()
firstname!: string;
@Column()
lastname!: string;
@Column()
email!: string;
@Column()
birthdate!: Date;
@Column()
gender!: string;
@Column()
created_at!: Date;
@Column()
updated_at!: Date;
@Column()
termsofuse!: boolean;
@Column()
active!: boolean;
}
Did I do something wrong ?
Thanks a lot !
You must use property with the UserInDB
type, so: const test = await typedKnex.query(PasswordInDB).innerJoinColumn((i) => i.user).getMany();
In the next version that I'm working on, there is a compilation check to make sure that your previous code doesn't compile.
(BTW, it looks like you're storing a plain text password. I would highly advise against that)
Okay thanks a lot !
Thanks for your work !
It's a bcrypt hash, I compare the new hash from the db request !
It's a bcrypt hash, I compare the new hash from the db request !
👍