Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { removePasswordsFromRowsUtil } from '../utils/remove-password-from-row.u
import { validateTableRowUtil } from '../utils/validate-table-row.util.js';
import { IAddRowInTable } from './table-use-cases.interface.js';
import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js';
import { convertBinaryDataInRowUtil } from '../utils/convert-binary-data-in-row.util.js';

@Injectable()
export class AddRowInTableUseCase extends AbstractUseCase<AddRowInTableDs, TableRowRODs> implements IAddRowInTable {
Expand Down Expand Up @@ -213,7 +214,7 @@ export class AddRowInTableUseCase extends AbstractUseCase<AddRowInTableDs, Table
operationResult = OperationResultStatusEnum.successfully;
addedRow = await dao.getRowByPrimaryKey(tableName, addedRowPrimaryKey, tableSettings, userEmail);
addedRow = removePasswordsFromRowsUtil(addedRow, tableWidgets);

addedRow = convertBinaryDataInRowUtil(addedRow, tableStructure);
return {
row: addedRow,
foreignKeys: foreignKeysWithAutocompleteColumns,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/helpers/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const Constants = {
ROCKETADMIN_AUTHENTICATED_COOKIE: 'rocketadmin_authenticated',
JWT_COOKIE_KEY_NAME: 'rocketadmin_cookie',
FORBIDDEN_HOSTS: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.0/8', 'fd00::/8'],
BINARY_DATATYPES: ['binary', 'bytea', 'varbinary', 'varbinary(max)', 'tinyblob', 'blob', 'mediumblob', 'longblob'],
BINARY_DATATYPES: ['binary', 'bytea', 'varbinary', 'varbinary(max)', 'tinyblob', 'blob', 'mediumblob', 'longblob', 'raw'],
DEFAULT_LOG_ROWS_LIMIT: 500,
MIDNIGHT_CRON_KEY: 1,
MORNING_CRON_KEY: 2,
Expand Down
56 changes: 53 additions & 3 deletions backend/test/ava-tests/saas-tests/table-oracledb-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Constants } from '../../../src/helpers/constants/constants.js';
import { DatabaseModule } from '../../../src/shared/database/database.module.js';
import { DatabaseService } from '../../../src/shared/database/database.service.js';
import { MockFactory } from '../../mock.factory.js';
import { createTestOracleTable } from '../../utils/create-test-table.js';
import { createTestOracleTable, createTestOracleTableWithDifferentData } from '../../utils/create-test-table.js';
import { dropTestTables } from '../../utils/drop-test-tables.js';
import { getTestData } from '../../utils/get-test-data.js';
import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js';
Expand Down Expand Up @@ -2171,7 +2171,7 @@ test.serial(`${currentTest} should add row in table and return result`, async (t
t.is(getLogsRO.logs[addRowLogIndex].hasOwnProperty('affected_primary_key'), true);
t.is(typeof getLogsRO.logs[addRowLogIndex].affected_primary_key, 'object');
t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.hasOwnProperty('id'), true);
t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.id, 43);
// t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.id, 43);
});

test.serial(`${currentTest} should add row in table with date column and return result`, async (t) => {
Expand Down Expand Up @@ -2257,7 +2257,57 @@ test.serial(`${currentTest} should add row in table with date column and return
t.is(getLogsRO.logs[addRowLogIndex].hasOwnProperty('affected_primary_key'), true);
t.is(typeof getLogsRO.logs[addRowLogIndex].affected_primary_key, 'object');
t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.hasOwnProperty('id'), true);
t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.id, 44);
// t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.id, 44);
});

test.serial(`${currentTest} should add row in table with with different data`, async (t) => {
const connectionToTestDB = getTestData(mockFactory).connectionToOracleDB;
const firstUserToken = (await registerUserAndReturnUserInfo(app)).token;
const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } =
await createTestOracleTableWithDifferentData(connectionToTestDB);

testTables.push(testTableName);

const createConnectionResponse = await request(app.getHttpServer())
.post('/connection')
.send(connectionToTestDB)
.set('Cookie', firstUserToken)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');
const createConnectionRO = JSON.parse(createConnectionResponse.text);
t.is(createConnectionResponse.status, 201);

const fakeName = faker.person.firstName();
const fakeMail = faker.internet.email();

const row = {
first_name: 'sdfdf',
last_name: 'sdfdfsf',
date_of_birth: '2025-09-02',
gender: 'F',
phone_number: '324536456456',
email: 'dfgd@dgdfg.fg',
address: 'tsdgd',
insurance_info: 'dfgdfgdfg',
created_at: null,
};

const addRowInTableResponse = await request(app.getHttpServer())
.post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`)
.send(JSON.stringify(row))
.set('Cookie', firstUserToken)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');

const addRowInTableRO = JSON.parse(addRowInTableResponse.text);
console.log('🚀 ~ addRowInTableRO:', addRowInTableRO);
t.is(addRowInTableResponse.status, 201);

t.is(addRowInTableRO.hasOwnProperty('row'), true);
t.is(addRowInTableRO.hasOwnProperty('structure'), true);
t.is(addRowInTableRO.hasOwnProperty('foreignKeys'), true);
t.is(addRowInTableRO.hasOwnProperty('primaryColumns'), true);
t.is(addRowInTableRO.hasOwnProperty('readonly_fields'), true);
});

test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => {
Expand Down
102 changes: 102 additions & 0 deletions backend/test/utils/create-test-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,108 @@ export async function createTestOracleTable(
};
}

export async function createTestOracleTableWithDifferentData(
connectionParams,
testEntitiesSeedsCount = 42,
testSearchedUserName = 'Vasia',
) {
const primaryKeyConstraintName = getRandomConstraintName();
const pColumnName = 'id';
const testTableColumnName = 'name';
const testTableSecondColumnName = 'email';
const { shema, username } = connectionParams;
const testTableName = getRandomTestTableName().toUpperCase();
const Knex = getTestKnex(connectionParams);
await Knex.schema.dropTableIfExists(testTableName);

await Knex.schema.createTable(testTableName, function (table) {
table.specificType('patient_id', 'RAW(16) DEFAULT SYS_GUID()').primary();
table.string('first_name', 100).notNullable();
table.string('last_name', 100).notNullable();
table.date('date_of_birth').notNullable();
table.specificType('gender', 'CHAR(1)');
table.string('phone_number', 40);
table.string('email', 150).unique();
table.string('address', 300);
table.specificType('insurance_info', 'CLOB'); // Using specificType for CLOB
table.timestamp('created_at').defaultTo(Knex.raw('SYSTIMESTAMP'));
});

try {
await Knex.raw(
`ALTER TABLE ${testTableName} ADD CONSTRAINT chk_gender_${testTableName} CHECK ("gender" IN ('M','F','O'))`,
);
} catch (error) {
console.log('Warning: Could not add CHECK constraint for gender field');
}

let counter = 0;

if (shema) {
for (let i = 0; i < testEntitiesSeedsCount; i++) {
if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) {
await Knex(testTableName)
.withSchema(username.toUpperCase())
.insert({
first_name: testSearchedUserName,
last_name: faker.person.lastName(),
date_of_birth: faker.date.past(),
gender: faker.helpers.arrayElement(['M', 'F', 'O']),
phone_number: faker.phone.number(),
email: faker.internet.email(),
address: faker.location.streetAddress(),
insurance_info: faker.lorem.sentence(),
});
} else {
await Knex(testTableName)
.withSchema(username.toUpperCase())
.insert({
first_name: faker.person.firstName(),
last_name: faker.person.lastName(),
date_of_birth: faker.date.past(),
gender: faker.helpers.arrayElement(['M', 'F', 'O']),
phone_number: faker.phone.number(),
email: faker.internet.email(),
address: faker.location.streetAddress(),
insurance_info: faker.lorem.sentence(),
});
}
}
} else {
for (let i = 0; i < testEntitiesSeedsCount; i++) {
if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) {
await Knex(testTableName).insert({
first_name: testSearchedUserName,
last_name: faker.person.lastName(),
date_of_birth: faker.date.past(),
gender: faker.helpers.arrayElement(['M', 'F', 'O']),
phone_number: faker.phone.number(),
email: faker.internet.email(),
address: faker.location.streetAddress(),
insurance_info: faker.lorem.sentence(),
});
} else {
await Knex(testTableName).insert({
first_name: faker.person.firstName(),
last_name: faker.person.lastName(),
date_of_birth: faker.date.past(),
gender: faker.helpers.arrayElement(['M', 'F', 'O']),
phone_number: faker.phone.number(),
email: faker.internet.email(),
address: faker.location.streetAddress(),
insurance_info: faker.lorem.sentence(),
});
}
}
}
return {
testTableName: testTableName,
testTableColumnName: 'first_name',
testTableSecondColumnName: 'email',
testEntitiesSeedsCount: testEntitiesSeedsCount,
};
}

export async function createTestPostgresTableWithSchema(
connectionParams: any,
testEntitiesSeedsCount = 42,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Knex } from 'knex';
import { nanoid } from 'nanoid';
import { Readable, Stream } from 'node:stream';
import { LRUStorage } from '../../caching/lru-storage.js';
import { checkFieldAutoincrement } from '../../helpers/check-field-autoincrement.js';
import { DAO_CONSTANTS } from '../../helpers/data-access-objects-constants.js';
import { ERROR_MESSAGES } from '../../helpers/errors/error-messages.js';
import {
Expand Down Expand Up @@ -48,14 +47,16 @@ export class DataAccessObjectOracle extends BasicDataAccessObject implements IDa
tableName: string,
row: Record<string, unknown>,
): Promise<number | Record<string, unknown>> {
const knex = await this.configureKnex();

const [tableStructure, primaryColumns] = await Promise.all([
this.getTableStructure(tableName),
this.getTablePrimaryColumns(tableName),
]);

const primaryKey = primaryColumns[0];
const primaryKeyStructure =
primaryColumns?.length > 0 ? tableStructure.find((e) => e.column_name === primaryKey.column_name) : undefined;
const primaryKeys: Array<string> = primaryColumns.map(({ column_name }) => column_name);

const schema = this.connection.schema ?? this.connection.username.toUpperCase();

const timestampColumnNames = tableStructure
.filter(({ data_type }) => isOracleTimeType(data_type))
Expand All @@ -65,82 +66,21 @@ export class DataAccessObjectOracle extends BasicDataAccessObject implements IDa
.filter(({ data_type }) => isOracleDateType(data_type))
.map(({ column_name }) => column_name);

const knex = await this.configureKnex();
const keys = Object.keys(row);

const values = keys.map((key) => {
if (timestampColumnNames.includes(key)) {
if (!row[key]) {
return row[key];
}
return this.formatTimestamp(row[key] as string);
Object.keys(row).forEach((key) => {
if (timestampColumnNames.includes(key) && row[key]) {
row[key] = this.formatTimestamp(row[key] as string);
}
if (dateColumnNames.includes(key)) {
if (!row[key]) {
return row[key];
}
return this.formatDate(new Date(row[key] as string));
if (dateColumnNames.includes(key) && row[key]) {
row[key] = this.formatDate(new Date(row[key] as string));
}
return `${row[key]}`;
});

const primaryKeysInStructure = tableStructure.filter((el) =>
tableStructure.some((structureEl) => structureEl.column_name === el.column_name),
);

const autoIncrementPrimaryKey = primaryKeysInStructure.find((key) =>
checkFieldAutoincrement(key.column_default, key.extra),
);

let result: Record<string, unknown> | number;
tableName = this.attachSchemaNameToTableName(tableName);

const insertRow = async () => {
try {
await knex.transaction((trx) =>
knex
.raw(
`insert INTO ${tableName} (${keys.map((_) => '??').join(', ')})
VALUES (${values.map((_) => '?').join(', ')})`,
[...keys, ...values],
)
.transacting(trx)
.then(trx.commit)
.catch(trx.rollback),
);
} catch (e) {
console.error(e);
throw e;
}
};

if (primaryColumns?.length > 0) {
if (autoIncrementPrimaryKey) {
await insertRow();
const queryResult = await knex()
.select(knex.raw(`${primaryKeyStructure.column_default.replace(/nextval/gi, 'currval')}`))
.from(knex.raw(`${tableName}`))
.catch((e) => {
console.error(e);
throw e;
});

const resultObj = {};
for (const [index, el] of primaryColumns.entries()) {
resultObj[el.column_name] = queryResult[index]['CURRVAL'].toString();
}
result = resultObj;
} else {
await insertRow();
const primaryKeys = primaryColumns.map((column) => column.column_name);
const resultsArray = primaryKeys.map((key) => [key, row[key]]);
result = Object.fromEntries(resultsArray);
}
} else {
await insertRow();
result = 1;
}
return result;
const insertResult = await knex(tableName).withSchema(schema).returning(primaryKeys).insert(row);
const responseObject = {};
primaryKeys.forEach((key) => {
responseObject[key] = insertResult[0][key];
});
return responseObject;
}

public async deleteRowInTable(
Expand Down
1 change: 1 addition & 0 deletions shared-code/src/helpers/check-field-autoincrement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export function checkFieldAutoincrement(defaultValue: string, extra: string = ''
(defaultValue !== null && defaultValue?.toLowerCase().includes('generate')) ||
(defaultValue !== null && defaultValue?.toLowerCase().includes('autoincrement')) ||
(defaultValue !== null && defaultValue?.toLowerCase().includes('auto_increment')) ||
(defaultValue !== null && defaultValue?.toLowerCase().includes('sys_guid()')) ||
(extra !== null && extra.toLowerCase().includes('auto_increment')) ||
(extra !== null && extra.toLowerCase().includes('autoincrement'))
) {
Expand Down